oauth-popup-flow
sidebar_position: 42 last_updated: "2025-10-17"
OAuth Redirect Flow (popup deprecated)
We replaced the popup-based OAuth flow with a simpler and more reliable top-level redirect flow. Popups caused issues across browsers and platforms; the redirect flow is easier to reason about and works consistently on mobile and desktop.
High-level flow
- User clicks "Sign in with Google" which calls
AuthContext.login(). AuthContext.login()fetchesGET /login/googlewithcredentials: 'include'.- The server sets short-lived
oauth_stateandoauth_code_verifiercookies and returns a JSON payload{ redirectUrl }. - The frontend performs a top-level navigation to the returned
redirectUrl. - After consent, Google redirects the browser to
/login/google/callback?code=...&state=.... - The callback route posts
{ code, state }toPOST /login/google/callback. The backend validates the state + PKCE, creates a session and sets the auth cookie, and returns{ success: true, user }. - The callback page updates the React Query
["auth","whoami"]cache with the returned user and navigates the app to/so authenticated UI appears immediately.
Backend behavior (unchanged)
GET /login/googlewill:- Generate
stateand PKCE verifier and store them inoauth_stateandoauth_code_verifierHttpOnly cookies (SameSite=Lax, short maxAge). - If the request is a top-level navigation (Sec-Fetch-Mode=navigate or
?mode=navigate) it will302redirect to the provider URL. Otherwise the endpoint returns JSON{ redirectUrl, state }for programmatic clients.
- Generate
POST /login/google/callbackvalidates thecodeandstate, exchanges the code for tokens, creates a user & session, sets the auth cookie, clears the oauth cookies and returns{ success: true, user }.
No backend changes are required for the redirect-based flow — the current implementation already supports both navigation redirects and fetch-based bootstrapping.
Frontend implementation notes
- Use
fetch('/login/google', { method: 'GET', credentials: 'include' })and thenwindow.location.href = redirectUrl(orassign) to perform the redirect after the fetch resolves (this ensures cookies are persisted before the provider redirect). - The callback component (already in
src/routes/login/google/callback.tsx) should POST{ code, state }to the backend. On success, update the React Query cache key['auth','whoami']with the returned user and then navigate the app to/using the app router (TanStack Router) so the authenticated state appears without a page reload.
Migration notes
- Popup route and support was removed from the codebase. If you still need backward compatibility, add a short-lived feature flag and keep the opener postMessage contract in the callback component.
- Keep
GET /login/googleunchanged — it still supportsmode=navigatefor server-side redirects.
Testing & QA
- Unit test: mock
GET /login/googleto return{ redirectUrl }and assert frontend navigates to the URL. - Callback unit test: mock
POST /login/google/callbackto return{ success: true, user }and assert React Query cache['auth','whoami']is updated and router navigates to/. - Manual QA: test flow on desktop and mobile browsers; test case where cookie is not set (simulate) and observe error handling.
Why we chose this
- Simpler: top-level navigation avoids popup blockers and cross-origin restrictions.
- More reliable on mobile and PWAs.
- Minimal code change: only
AuthContext.login()and the callback route need small updates; backend stayed the same.
If you need me to also update any other docs or remove leftover references to "popup" throughout the docs site, I can do that in a follow-up.