Skip to content

fix(client): stop the token refresh loop when the browser clock runs ahead - #129

Merged
nicknisi merged 3 commits into
mainfrom
riker/185-fix-https-github-com-workos-authkit-tans
Oct 19, 2026
Merged

nicknisi merged 3 commits into
mainfrom
riker/185-fix-https-github-com-workos-authkit-tans

Conversation

@nicknisi

Copy link
Copy Markdown
Member

Fix #128: the client tokenStore (src/client/tokenStore.ts) refreshes the access token in a zero-delay loop when the browser clock runs ahead of WorkOS by more than the token lifetime minus the expiry buffer (271 s or more for a 300 s token), and also with a correct clock when a token's whole lifetime is shorter than the buffer. First reproduce it with a failing test, then fix it so that (1) getRefreshDelay always applies the MIN_REFRESH_DELAY_SECONDS floor (15 s) and never returns 0, and (2) expiry is judged on WorkOS's clock: record the browser clock's offset from the iat of each token the refresh action (refreshAccessTokenAction) has just minted, and use that offset in parseToken/isExpiring. Tokens that come from getAccessTokenAction or the cookie can be minutes old, so they must not set the offset. With the fix, a skewed client returns to the normal cadence of one refresh per (lifetime − buffer), and a 20 s token refreshes every 15 s instead of looping. Commit with a Conventional Commits message (the repo releases with release-please), e.g. fix(client): stop the token refresh loop when the browser clock runs ahead, with Fixes #128 in the body and a line crediting the reporter, @MatiasVerdier, for the report and the suggested patch.

Riker's check git merge-base --is-ancestor c19b44fe8718f389105994bdd7095fd840054f43 HEAD && test -z "$(git diff --name-only c19b44fe8718f389105994bdd7095fd840054f43 HEAD -- . ':!src/client/tokenStore.ts' ':!src/client/tokenStore.spec.ts')" && ! git diff --quiet c19b44fe8718f389105994bdd7095fd840054f43 HEAD -- src/client/tokenStore.ts && ! git diff --quiet c19b44fe8718f389105994bdd7095fd840054f43 HEAD -- src/client/tokenStore.spec.ts && test -n "$(git log --format=%H --grep='Fixes #128' c19b44fe8718f389105994bdd7095fd840054f43..HEAD)" && test -n "$(git log --format=%H --grep='MatiasVerdier' c19b44fe8718f389105994bdd7095fd840054f43..HEAD)" && pnpm run typecheck && pnpm run lint && pnpm run format:check && pnpm test passed.

Opened as a draft by Riker (job 185).

…ahead

getRefreshDelay now always applies the 15 s floor instead of returning 0
when a token is inside its expiry buffer, so a token shorter than the
buffer refreshes every 15 s rather than in a zero-delay loop.

Expiry is now judged on WorkOS's clock: each token minted by
refreshAccessTokenAction records the browser's offset from its `iat`
(measured from the request start, so latency errs toward refreshing
early). Tokens from getAccessTokenAction never set the offset. A token
timer that finds its token still fresh (the wall clock jumped, e.g. NTP
corrected a fast clock) refreshes to re-measure instead of stalling.

Fixes #128

Thanks to @MatiasVerdier for the report and the suggested patch.
@nicknisi
nicknisi marked this pull request as ready for review October 18, 2026 23:17
@greptile-apps

greptile-apps Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Critical impact] The PR appears safe to merge; the retained-token issue is fixed and no new actionable issues were found.

Summary

Stops rapid token refreshes when the browser clock runs ahead.

  • Token refreshes use WorkOS time and wait at least 15 seconds.
  • The store rechecks tokens kept through a long idle gap.

Reviews (3) · Last reviewed commit: "fix(client): refresh a token retained th..." · Reviewed by Greptile

Comment thread src/client/tokenStore.ts
clearToken() now resets the offset, so a clock corrected between
sessions can't make the next session's token look fresh past its real
expiry. The offset is also dropped when the store comes back after an
idle gap with no listeners longer than the 15 s refresh floor (a
wall-clock jump during the gap counts), while quick unmount/remount
churn keeps it.

Refs #128
Comment thread src/client/tokenStore.ts
…ing it

Dropping the clock offset after an idle gap made a token kept through
the gap look fresh again on a clock that is still behind WorkOS (600 s
behind, 400 s gap: expired 100 s ago, judged valid for 500 s more).
When the offset is dropped, the retained token is now marked unverified
and both getters refresh it before handing it out. Gaps under the 15 s
floor keep the offset and make no extra refresh.

Refs #128
@nicknisi
nicknisi merged commit 1321871 into main Oct 19, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

tokenStore refreshes in a zero-delay loop when the browser clock runs ahead of WorkOS

2 participants