Skip to content

Auth: Fix re-entrant /token call after OAuth code exchange#22097

Merged
leekelleher merged 1 commit intomainfrom
v17/bugfix/fix-reentrant-token-call-after-code-exchange
Mar 11, 2026
Merged

Auth: Fix re-entrant /token call after OAuth code exchange#22097
leekelleher merged 1 commit intomainfrom
v17/bugfix/fix-reentrant-token-call-after-code-exchange

Conversation

@iOvergaard
Copy link
Copy Markdown
Contributor

What

Moves the #inSessionUpdateCallback re-entrancy guard into #setSessionLocally() so all callers are covered, not just the Web Locks lock callback.

Why (the bug)

PR #22087 added #inSessionUpdateCallback in makeRefreshTokenRequest()'s lock callback to prevent a double /token call when session$ fired synchronously inside #updateSession(). But completeAuthorizationRequest() calls #setSessionLocally() directly — without ever setting the flag.

With KeepUserLoggedIn=true and a short TimeOut (e.g. 00:01:00, giving expiresIn=15s), secondsUntilWarning=0 so #onSessionExpiring() fires synchronously from inside #setSessionLocally(). This path was unguarded, producing a second /token POST immediately after the code exchange 200.

The same gap existed in the no-Web-Locks fallback path in makeRefreshTokenRequest().

Fix

Move the try/finally guard into #setSessionLocally() itself. Remove the now-redundant wrapper in makeRefreshTokenRequest()'s lock callback. The guard at the top of makeRefreshTokenRequest() (if (this.#inSessionUpdateCallback) return true) is unchanged — it still works because the flag is now set before session$ emits.

Call site Before After
makeRefreshTokenRequest() lock callback → #updateSession()#setSessionLocally() ✅ guarded externally ✅ guarded in #setSessionLocally()
completeAuthorizationRequest()#setSessionLocally() ❌ unguarded (the bug) ✅ guarded
makeRefreshTokenRequest() no-locks fallback → #updateSession()#setSessionLocally() ❌ unguarded ✅ guarded

Testing

Verified with KeepUserLoggedIn=true + TimeOut=00:01:00:

  • Exactly 1 /token [200] after code exchange (was 2)
  • No ID2019 errors
  • No spurious [Auth] Session expiring, auto-refreshing immediately after login
  • Proactive refresh cycle produces 1 /token [200] per cycle

🤖 Generated with Claude Code

Move #inSessionUpdateCallback guard into #setSessionLocally() so all
callers are protected, not just makeRefreshTokenRequest()'s lock callback.

Previously, completeAuthorizationRequest() called #setSessionLocally()
directly without setting the flag. With keepUserLoggedIn=true and a short
TimeOut, session$ observers fired synchronously inside #setSessionLocally,
triggering #onSessionExpiring → validateToken() → makeRefreshTokenRequest()
before #inSessionUpdateCallback was ever set — causing a second /token call
immediately after the initial code exchange 200.

The no-Web-Locks fallback path in makeRefreshTokenRequest() had the same
gap. Moving the flag into #setSessionLocally() covers all call sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an auth flow re-entrancy bug where session observers could synchronously trigger a second /token request immediately after an OAuth code exchange (and in the no-Web-Locks fallback), by ensuring the re-entrancy guard applies to all session-setting call paths.

Changes:

  • Moves the #inSessionUpdateCallback try/finally guard into #setSessionLocally() so it covers all callers.
  • Removes the now-redundant guard wrapper around #updateSession() in the Web Locks refresh callback.

@leekelleher leekelleher self-requested a review March 11, 2026 13:21
@leekelleher leekelleher enabled auto-merge (squash) March 11, 2026 13:22
@leekelleher leekelleher merged commit 651cb0a into main Mar 11, 2026
34 checks passed
@leekelleher leekelleher deleted the v17/bugfix/fix-reentrant-token-call-after-code-exchange branch March 11, 2026 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants