Skip to main content

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

  1. User clicks "Sign in with Google" which calls AuthContext.login().
  2. AuthContext.login() fetches GET /login/google with credentials: 'include'.
  3. The server sets short-lived oauth_state and oauth_code_verifier cookies and returns a JSON payload { redirectUrl }.
  4. The frontend performs a top-level navigation to the returned redirectUrl.
  5. After consent, Google redirects the browser to /login/google/callback?code=...&state=....
  6. The callback route posts { code, state } to POST /login/google/callback. The backend validates the state + PKCE, creates a session and sets the auth cookie, and returns { success: true, user }.
  7. 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/google will:
    • Generate state and PKCE verifier and store them in oauth_state and oauth_code_verifier HttpOnly cookies (SameSite=Lax, short maxAge).
    • If the request is a top-level navigation (Sec-Fetch-Mode=navigate or ?mode=navigate) it will 302 redirect to the provider URL. Otherwise the endpoint returns JSON { redirectUrl, state } for programmatic clients.
  • POST /login/google/callback validates the code and state, 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 then window.location.href = redirectUrl (or assign) 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/google unchanged — it still supports mode=navigate for server-side redirects.

Testing & QA

  • Unit test: mock GET /login/google to return { redirectUrl } and assert frontend navigates to the URL.
  • Callback unit test: mock POST /login/google/callback to 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.