Repository navigation
fix(client): stop the token refresh loop when the browser clock runs ahead - #129
Merged
nicknisi merged 3 commits intoOct 19, 2026
Merged
Conversation
…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
marked this pull request as ready for review
October 18, 2026 23:17
Contributor
[Critical impact] The PR appears safe to merge; the retained-token issue is fixed and no new actionable issues were found. SummaryStops rapid token refreshes when the browser clock runs ahead.
Reviews (3) · Last reviewed commit: "fix(client): refresh a token retained th..." · Reviewed by Greptile |
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
…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
gjtorikian
approved these changes
Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
iatof 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, withFixes #128in 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 testpassed.Opened as a draft by Riker (job 185).