Skip to content

fix(auth-next-server): deduplicate concurrent token refresh to prevent invalid_grant#2865

Open
dom-murray wants to merge 1 commit intomainfrom
GAM-113
Open

fix(auth-next-server): deduplicate concurrent token refresh to prevent invalid_grant#2865
dom-murray wants to merge 1 commit intomainfrom
GAM-113

Conversation

@dom-murray
Copy link
Copy Markdown
Contributor

@dom-murray dom-murray commented Apr 23, 2026

Summary

  • OAuth refresh tokens are single-use, but NextAuth has no built-in concurrency protection — parallel requests hitting the same expired token all fire independent refresh calls, causing the second+ callers to get 400 invalid_grant and have RefreshTokenError set on their session
  • Added promise deduplication in refreshAccessToken: concurrent callers with the same refresh token share one in-flight promise instead of each making a separate network request
  • The internal HTTP logic moved to doRefreshAccessToken; the public API is unchanged

How it works

Request A → refreshPromises.get(key) = undefined → starts fetch, stores promise
Request B → refreshPromises.get(key) = Promise<...> → awaits same promise
Request C → refreshPromises.get(key) = Promise<...> → awaits same promise

Provider receives 1 request (not 3) → all callers receive the same refreshed tokens

The map entry is cleaned up via .finally() so the next genuine refresh (after the token rotates) works normally.

Test plan

  • Sign in, open 3 tabs simultaneously — all tabs should remain authenticated after token expiry rather than one succeeding and others getting RefreshTokenError
  • Verify middleware + route handler concurrent path doesn't trigger double refresh
  • Confirm forceRefresh (zkEvm registration flow) still works correctly via the trigger === 'update' path in the JWT callback (that path calls refreshAccessToken directly and benefits from the same deduplication)

🤖 Generated with Claude Code


Note

Medium Risk
Touches server-side OAuth token refresh behavior; while the API is unchanged, incorrect deduping/cleanup could cause stale refresh results or masking of refresh errors under load.

Overview
Prevents invalid_grant errors under parallel requests by deduplicating concurrent refreshAccessToken calls per clientId+refreshToken, ensuring only one refresh request is sent and other callers await the same promise.

Refactors the actual HTTP refresh logic into an internal doRefreshAccessToken helper and cleans up the in-flight promise cache via .finally() so subsequent refreshes proceed normally.

Reviewed by Cursor Bugbot for commit ccecbe5. Bugbot is set up for automated code reviews on this repo. Configure here.

…prevent invalid_grant

OAuth refresh tokens are single-use. Without concurrency protection, parallel
requests hitting the same expired token each fire a separate refresh, causing
the second caller to receive a 400 invalid_grant and set RefreshTokenError on
the session. Promise deduplication ensures all concurrent callers share one
in-flight refresh per token.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@dom-murray dom-murray requested a review from a team as a code owner April 23, 2026 04:01
@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Apr 23, 2026

View your CI Pipeline Execution ↗ for commit ccecbe5

Command Status Duration Result
nx release publish --tag alpha ✅ Succeeded 46s View ↗
nx run-many -p @imtbl/sdk,@imtbl/checkout-widge... ✅ Succeeded 2m 11s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-23 04:15:44 UTC

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.

1 participant