OAuth · intermediate
OAuth works in one browser but not another: compare callback storage
OAuth succeeds in one browser but fails in another because the browser-specific storage of state, nonce, session, and PKCE verifiers diverges between the requestor and the callback. This guide isolates the browser that breaks the round-trip and compares cookie, sessionStorage, localStorage, and service worker behavior against the OAuth provider's requirements.
The symptoms
- •OAuth sign-in completes in Chrome but returns an invalid_state, state_mismatch, or missing_code error in Safari or Firefox for the same user account.
- •Callback URL contains the authorization code but the client rejects it with a redirect_uri mismatch only when reached from a specific browser.
- •Authentication works in a normal window but fails in a private/incognito window of the same browser, even with cookies allowed for the relying party.
- •Login succeeds on a desktop browser but fails on the mobile browser of the same OS family, with identical network traces on the client side except for Set-Cookie attributes.
- •OAuth flow breaks after a browser update that changes third-party cookie defaults, SameSite enforcement, or ITP/partitioned storage rules.
- •State parameter appears in the authorization request but is empty or absent in the callback request, even though the provider URL still contains it.
Likely causes
- •The state, nonce, or PKCE verifier was stored in sessionStorage, which is tab-isolated, while the callback returns in a new tab opened by the identity provider.
- •Cookies carrying the OAuth state are blocked by third-party cookie restrictions or are issued without SameSite=None; Secure, so the browser discards them on the cross-site callback leg.
- •Intelligent Tracking Prevention or Enhanced Tracking Protection partitions or purges the storage used to persist the pre-authorization state before the callback completes.
- •The relying party relies on document.cookie, which is unavailable across schemes, sandboxed iframes, or non-HTTP document contexts that one of the tested browsers uses.
- •Two browser profiles, devices, or extension sets disagree on clock skew or on which authorization endpoint URL is considered a valid redirect target.
- •Browser-specific popup blockers, navigation handlers, or service workers intercept the callback redirect and prevent the relying party's state-checking script from executing.
First ten minutes
- 01Reproduce the failure in the failing browser using the exact account, identity provider, and relying party origin that succeed elsewhere; note whether the issue is browser-specific or profile-specific.
- 02Open the browser's developer tools on the relying party origin before the sign-in attempt and preserve storage across reloads to confirm what persists between the request and the callback.
- 03Capture the authorization request URL from the network tab and the callback request URL from the identity provider, then diff state, nonce, code_challenge, redirect_uri, and scope values for unexpected encoding changes.
- 04List every cookie, localStorage entry, sessionStorage entry, and IndexedDB record set during the authorization request, and verify which survive until the callback is processed.
- 05Disable extensions and content blockers in the failing browser and re-run the flow once to isolate whether the divergence is browser policy or third-party interference.
- 06Compare the Set-Cookie attributes returned by the relying party with what the failing browser accepts, focusing on Secure, HttpOnly, SameSite, and Domain qualifiers documented for Set-Cookie.
Evidence to collect
- •Network HAR or saved trace covering the authorization redirect, the callback request, and any token exchange calls, with query string and Set-Cookie headers preserved verbatim.
- •Browser storage snapshots showing which keys exist under cookies, localStorage, sessionStorage, and IndexedDB at the moment the callback URL is loaded.
- •Screenshots or copies of the exact error code or message rendered by the relying party and the identity provider in the failing browser.
- •Browser version, profile state (signed-in vs. private), and a list of active extensions or content blockers captured for the failing and passing browsers.
- •Cookie attribute matrix from Set-Cookie for both browsers, with the relevant directives called out per the MDN Set-Cookie reference.
Where to look
- •Boundary between the relying party origin and the identity provider origin, where same-site classification and third-party cookie policy are evaluated per request.
- •Storage layer of the relying party's web application, specifically the persistence mechanism used to remember the state, nonce, or PKCE verifier across the redirect.
- •Browser-side cookie jar and partitioned storage, including the ITP, Enhanced Tracking Protection, or third-party cookie controls that govern cross-site storage.
- •Callback handler script that reads stored state and compares it to the state returned by the identity provider, since storage loss here produces state_mismatch errors.
- •Browser privacy settings page and any device management profile that may override defaults for third-party storage or redirect handling.
Diagnostic steps
- 01Classify the failure by error class: invalid_state, state_mismatch, missing_code, or redirect_uri mismatch; each points to a different part of the storage-to-callback path.
- 02Diff the request URLs between browsers to confirm redirect_uri, scope, and state round-trip exactly, then identify any query parameter that the failing browser normalizes away.
- 03Inspect the Set-Cookie response on the authorization request and verify whether the cookie would be accepted on the cross-site callback leg per the directives listed in the MDN Set-Cookie reference.
- 04Read the relying party code path that issues the authorization redirect and identify which storage API it uses to retain state, nonce, or PKCE verifier values.
- 05On the callback, read the same storage API in the failing browser and determine whether the value is missing, truncated, or replaced by a value from a different tab or profile.
- 06Repeat the flow in a private window of the failing browser to determine whether cookie or storage partitioning is the active cause versus a profile-level state divergence.
- 07Toggle third-party cookie or tracking protection settings in the failing browser and re-run to confirm the dependency before recommending a configuration change.
Common mistakes
- •Assuming the OAuth provider is at fault when the callback handler cannot read the state it issued because of a browser storage restriction.
- •Storing the OAuth state in sessionStorage and then launching the identity provider in a new tab, which cannot see the originating tab's sessionStorage.
- •Issuing the relying party's session cookie without Secure and SameSite=None, which causes modern browsers to omit it on the cross-site callback leg.
- •Comparing URLs by eye instead of by normalized query string, missing a percent-encoding or case difference that only one browser preserves.
- •Concluding the problem is network-related when the failing browser actually receives the callback but the relying party's JavaScript has been blocked or replaced by a service worker.
- •Applying a global third-party cookie exception without isolating the relying party origin, which masks the storage mismatch without producing a durable fix.
Safe fixes
- •If the state is stored in sessionStorage but the callback returns in a new tab, move persistence to a same-site cookie or localStorage that the callback's tab can read, then verify with a HAR trace that the value is present on the callback request.
- •If the Set-Cookie attributes on the relying party omit Secure or SameSite=None for cross-site callbacks, update the cookie to include both directives where the schema permits and re-test in the failing browser.
- •If third-party storage is partitioned, adopt the Storage Access API or the identity provider's recommended handshake so the callback's tab can retrieve the pre-authorization state.
- •If the relying party relies on document.cookie for a same-origin callback, confirm that the callback page is served from the same origin that issued the state and is not inside a sandboxed iframe or a different scheme.
- •If extensions are intercepting redirects, reproduce with extensions disabled and then narrow to the specific extension before recommending a userscript or allowlist change to the team that owns the extension.
- •If PKCE verifier storage is the divergence, persist the verifier using the same mechanism as the state value and confirm the verifier length and encoding match what the authorization request sent.
Prove the fix
- 01Run the full OAuth round-trip in the previously failing browser with developer tools open and confirm that the callback request shows the expected state, nonce, or PKCE verifier value in storage before the token exchange begins.
- 02Capture a HAR trace from both browsers showing identical state, nonce, code_challenge, and redirect_uri values on the authorization request and the callback request.
- 03Verify the relying party no longer returns invalid_state, state_mismatch, or redirect_uri errors when the flow completes in a private window of the previously failing browser.
- 04Repeat the flow with the browser's third-party cookie or tracking protection settings at their default values and confirm the sign-in still succeeds.
- 05Re-run the flow across the two browsers under identical conditions and confirm that the identity provider's success page and the relying party's post-login state match within the same session.
Prevention and next steps
- •Persist OAuth state, nonce, and PKCE verifier in a storage layer whose lifetime and cross-tab visibility match the redirect path the identity provider actually uses, and document that choice in the relying party's auth design notes.
- •Issue relying party cookies with the Secure and SameSite directives required for cross-site callbacks, and review the Set-Cookie contract whenever browser defaults change.
- •Add automated checks that exercise the OAuth flow in at least two browser engines and in a private window, and fail the build when state or PKCE values are missing on the callback.
- •Track browser storage and cookie policy changes per release, and treat changes to third-party cookie defaults, ITP, and Enhanced Tracking Protection as input to the auth code review checklist.
Safe commands and checks
Open the relying party in the failing browser, launch developer tools, and inspect Application > Storage to list cookies, localStorage, sessionStorage, and IndexedDB entries before and after the OAuth redirect. In the failing browser's Network panel, filter for the authorization endpoint and the callback request, then export the trace so the Set-Cookie and Location headers can be diffed against the passing browser. In the passing browser, repeat the same Application and Network inspection so the two traces can be compared at the same request boundaries. From the exported trace, list each Set-Cookie directive and confirm Secure, HttpOnly, SameSite, Path, and Domain match the relying party's intent before changing any cookie code. Document the failing browser version, profile state, and active extensions in the bug report so future regressions can be reproduced without guessing the environment.