If opening a second tab causes an unexpected sign-in prompt, check whether both tabs tried to spend the same rotating refresh token. A request-level retry can look like token reuse to the authorization server. The fix is not simply a longer token lifetime or disabling reuse detection: identify who owns the current token, coordinate refresh at that scope, and define what happens when a response is lost.
This guide covers OAuth refresh-token rotation, with Auth0’s documented overlap behavior as a specific example. It is not a universal description of every identity provider or SDK. The timelines and implementation criteria are our engineering synthesis, not measurements of a running application. Sources were checked on October 31, 2026.
Why can a legitimate second request invalidate the session?
Under the rotation mechanism described in RFC 9700 §4.14.2, a refresh exchange replaces the token and invalidates its predecessor while retaining their relationship. Reuse lets the server detect possible compromise; it cannot identify the legitimate participant from the reused token alone. For refresh tokens issued to public clients, RFC 9700 requires rotation or sender-constraining. Disabling replay protection to avoid errors is not a substitute.
Consider this fictional sequence with no overlap allowance. R0 and R1 are labels, not real credentials:
- Tabs A and B both believe R0 is current.
- A exchanges R0. The server issues R1 and invalidates R0.
- Before learning about R1, B sends R0.
- With family-level reuse detection, the server invalidates the family. A’s newly stored R1 can no longer refresh it.
Auth0 documents this family invalidation behavior and the resulting need to reauthenticate. That is a statement about future refreshes, not a promise that every previously issued access token instantly stops working. Its own example distinguishes the access token’s expiry from the failed subsequent refresh. Auth0 reuse detection
The figure shows a possible race under the stated policy, not evidence that an observed failure was benign. A stolen token can also produce reuse. Investigate rather than suppressing the signal.
Does an overlap window solve the race?
It can absorb some close retries, but time is not the only condition. Auth0’s configuration guide says its overlap period permits reuse of the immediately previous token; presenting the second-to-last token triggers breach detection. During allowed overlap, a new rotating token is issued. Do not assume repeated requests return an identical successor or that every older token becomes reusable. Auth0 overlap configuration
For example, if the sequence has advanced R0 → R1 → R2, a delayed participant holding R0 is two generations behind. A large overlap duration is not evidence that this request is permitted. Check the provider’s exact rule, the SDK version, and the active application policy.
Our operational recommendation: treat overlap as a bounded recovery trade-off, not your concurrency mechanism. Choose it only with documented behavior and measured request/response delays. Do not copy a sample duration as a security recommendation.
Why doesn’t a shared promise in my interceptor fix it?
A single in-flight promise can combine concurrent callers inside that JavaScript instance. It does not, by itself, coordinate another tab, a worker, or a second backend process. First draw the ownership boundary:
| Where the same refresh state is used | Coordination question |
|---|---|
| Several calls in one tab | Do all callers use the same refresh owner, or can another interceptor bypass it? |
| Tabs and workers | Can they coordinate within the same supported browser lock/storage scope and reread current state afterward? |
| Backend instances sharing a session | Is ownership enforced across instances, rather than by a process-local mutex? |
| Separate devices or sessions | Do they actually share a token family? Avoid inventing a global per-user lock when credentials are independent. |
Web Locks is a secure-context API that provides coordination for cooperating browser agents in the applicable storage scope. It is not a cross-device or distributed-server lock, nor does it protect against hostile same-origin code. The W3C Web Locks draft describes storage-bucket scope and cooperative locking; verify browser support and partitioning for your deployment.
A lock is also not a token-sharing architecture. If tokens live only in another tab’s memory, acquiring a lock does not make its updated state accessible. Prefer your provider’s supported SDK/session design; do not move credentials into broadly readable persistent storage merely to make synchronization easier.
What belongs inside the coordinated operation?
Our design criterion is read → decide → exchange → commit, not just “lock the HTTP call.” A waiting participant should reread authoritative state after acquiring ownership. It may find that another participant already refreshed, so no exchange is necessary. RFC 6749 §6 requires replacing the old refresh token when a new one is returned.
For a server-owned session, review these invariants with your implementation team:
- Associate the operation with the actual session/token-family state, not an untrusted caller-supplied owner key.
- Commit the replacement before releasing ownership or telling waiting callers refresh succeeded.
- Prevent a delayed writer from overwriting a newer generation. A version check can reject stale writes; it cannot undo a second exchange already sent upstream.
- Define owner-crash and timeout behavior. A lock lease expiring while its owner still runs can admit a second caller; a mutex alone is not an exactly-once guarantee.
- Notify waiting callers using the established session design. Do not broadcast raw refresh tokens as a convenient synchronization message.
These are review criteria, not a drop-in protocol. The right storage, transaction and lock design depends on where credentials live and which party owns them.
What if the server rotated the token but the response disappeared?
A client timeout does not prove that the exchange failed. In a fictional lost-response case, the server moves R0 → R1, but the client still holds R0. Blindly retrying R0 can enter reuse handling; serializing callers does not recover the missing response.
Use the provider’s documented recovery or overlap semantics, if applicable. If you cannot establish a valid current state, take the supported reauthentication path rather than repeatedly replaying a stale credential. Our webhook crash-window guide discusses the related distinction between an operation happening and its acknowledgement arriving; a token endpoint is not thereby guaranteed to support an idempotency key.
Also, invalid_grant is not proof of a race or theft. RFC 6749 §5.2 includes several causes, such as an invalid, expired or revoked refresh token and client-binding problems. Correlate safe request identifiers, timestamps, session generations and provider error/event categories. Keep token values out of logs and shared diagnostics; see our HAR-sharing checklist.
What should I verify before calling this fixed?
Use an authorized local or test-tenant setup with synthetic identities. These are proposed acceptance cases, not experiments performed for this article.
- Two callers start from one generation: only the intended owner exchanges it, and the waiter observes committed state.
- A sleeping tab resumes after two rotations: it does not submit its stale snapshot first.
- The refresh response is lost: retries follow documented recovery rules and terminate rather than loop.
- The owner crashes before storing the response: the application fails into a defined recovery path, not false success.
- A delayed response arrives after a newer state: it cannot replace that newer state.
- Confirmed reuse or revocation remains a terminal refresh condition requiring the provider-supported recovery flow.
Measure refresh failures per refresh attempt, recovery prompts per affected session, and the share attributable to verified concurrency. Keep unknown causes separate. Fewer prompts alone do not prove a safer design: a change that merely stops detecting reuse can improve that number while weakening protection.
Sources & scope
OAuth standards and Auth0 documentation review, original diagrams, and fictional timelines. No identity-provider requests or live attack tests were performed.
- IETF RFC 9700 — OAuth 2.0 Security Best Current Practice
- IETF RFC 6749 — Refreshing an Access Token
- Auth0 — Configure Refresh Token Rotation
- Auth0 — Refresh Token Rotation
- W3C — Web Locks API
Publishing details & corrections
Prepared with AI assistance. Sources, any tests performed, and their limitations are described in the article.
No material corrections recorded for this version.