Skip to content

Fix: Transaction-keyed cookies to prevent multi-tab OAuth state race …#231

Open
tanya732 wants to merge 1 commit into
v2from
fix/multi-tab-state-cookie-race-condition
Open

Fix: Transaction-keyed cookies to prevent multi-tab OAuth state race …#231
tanya732 wants to merge 1 commit into
v2from
fix/multi-tab-state-cookie-race-condition

Conversation

@tanya732
Copy link
Copy Markdown
Contributor

Summary

  • Fixes multi-tab login race condition where concurrent OAuth flows overwrite each other's state/nonce cookies, causing both tabs to fail with state mismatch errors
  • Transaction-keyed cookies: State and nonce cookies now include the state value in the cookie name (e.g., com.auth0.state.{state}), isolating each login flow
  • Backward-compatible: Falls back to reading legacy fixed-name cookies (com.auth0.state, com.auth0.nonce) for in-flight transactions during rolling upgrades

Problem

The SDK stored state and nonce in fixed-name cookies (com.auth0.state, com.auth0.nonce). When a user opens multiple tabs, each login overwrites the previous tab's cookie. On callback, getOnce() destructively reads and deletes the cookie — if the "wrong" tab's callback arrives first, it consumes the other tab's state, causing both flows to fail.

This mirrors a fix already shipped in auth0.js and auth0-server-python.

Changes

File Change
StorageUtils.java Added transactionStateKey(state), transactionNonceKey(state), and TRANSACTION_COOKIE_MAX_AGE constant
TransientCookieStore.java storeState/storeNonce now write transaction-keyed cookies; getState/getNonce look up by state param with legacy fallback
AuthorizeUrl.java Passes state to storeNonce() (nonce cookie is keyed by state, not nonce)
RequestProcessor.java Passes state to getVerifiedTokens()getNonce()
## Test plan                                                                                                                                                                                                                                                                                                                                                                                                                   
  • All existing tests updated and passing
  • New tests verify multi-tab isolation (two concurrent state cookies coexist)
  • New tests verify correct cookie is consumed without affecting sibling transactions
  • Legacy fallback tested (old fixed-name cookies still work)
  • Null/missing state parameter handled gracefully
  • SameSite=None fallback cookies use transaction-keyed names

@tanya732 tanya732 requested a review from a team as a code owner May 21, 2026 11:01
* Max-Age for transaction cookies in seconds (10 minutes).
* Orphaned cookies from abandoned login flows will auto-expire.
*/
static final int TRANSACTION_COOKIE_MAX_AGE = 600;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this used somewhere?

@@ -81,27 +81,29 @@ public void shouldSetAudience() {
@Test
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could we add a few more tests to strengthen coverage of the multi-tab scenario for nonce?

  • A nonce isolation test (similar to shouldIsolateMultipleTransactions but for nonce) - store two nonces keyed by different states, retrieve each independently, confirm they don't interfere.
  • A nonce preference test (similar to shouldPreferTransactionKeyedOverLegacy but for nonce) - when both com.auth0.nonce.stateA and legacy com.auth0.nonce exist, confirm the transaction-keyed one wins.
  • An end-to-end multi-tab test that combines state + nonce together - two concurrent login flows storing their cookies, then each callback retrieves its own state and nonce correctly without affecting the other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants