Skip to content

fix: navigate directly to Google authorize endpoints#44

Merged
amrtgaber merged 1 commit into
mainfrom
fix/google-oauth-csp
May 27, 2026
Merged

fix: navigate directly to Google authorize endpoints#44
amrtgaber merged 1 commit into
mainfrom
fix/google-oauth-csp

Conversation

@amrtgaber
Copy link
Copy Markdown
Contributor

Symptom

Production Google sign-in is broken with a CSP violation:

Connecting to 'https://accounts.google.com/o/oauth2/v2/auth?...' violates the following Content Security Policy directive: "connect-src 'self' http://localhost:* https://*.criticalbit.gg ..."

Same blast radius hits the "Connect Google" button on /profile (the associate flow).

Cause

`handleGoogleLogin` and `handleConnect("google")` both did `api.get('.../authorize').json()` and then navigated to the returned `authorization_url`. In production the authorize endpoint returns a 302 to accounts.google.com; Ky's fetch follows that redirect automatically, and the browser blocks the cross-origin call because `accounts.google.com` isn't in `connect-src`. The fix already exists in this codebase for Steam — commit `c8d1e2d fix: navigate directly to Steam authorize endpoint` documents exactly this trap.

API code (`app/routers/auth_providers.py:294-310,369-382`) confirms `login_authorize` and `associate_authorize` share the same dev-JSON / prod-302 behavior across both providers, so this aligns Google's handlers with how Steam already works.

Fix

Two call sites, both switched from fetch-then-navigate to direct navigation. The browser navigates to the API, the API 302s, the browser follows the navigation redirect — `connect-src` doesn't apply to navigation.

```diff
-async function handleGoogleLogin() {

  • try {
  • if (redirect) localStorage.setItem("auth_redirect", redirect)
  • const res = await api.get("auth/google/authorize").json<{ authorization_url: string }>()
  • window.location.href = res.authorization_url
  • } catch (error) { ... toast.error ... }
    -}
    +function handleGoogleLogin() {
  • if (redirect) localStorage.setItem("auth_redirect", redirect)
  • window.location.href = `${baseUrl}/auth/google/authorize`
    +}
    ```

Same shape for the Google branch of `handleConnect` in profile-page.tsx.

Test plan

  • `pnpm test:run` — 45 tests pass locally.
  • Production: click "Sign in with Google" on auth.criticalbit.gg → should redirect to Google's OAuth consent screen, no CSP error.
  • Production: click "Connect" next to Google on /profile (when signed in) → same redirect chain.

Related

  • `c8d1e2d` — original Steam fix for the same bug; this PR brings Google to parity.

`handleGoogleLogin` and the Google branch of `handleConnect` both called
`api.get('.../authorize').json()` and then navigated to the returned
`authorization_url`. In production the authorize endpoint returns a 302
to accounts.google.com, which Ky's fetch follows automatically — and the
browser blocks the cross-origin call because accounts.google.com isn't
in our CSP connect-src.

Switch both to direct navigation (mirroring the existing Steam pattern
in c8d1e2d). The browser navigates to the API, the API 302s, the browser
follows the redirect — CSP connect-src doesn't apply to navigation.

API code (app/routers/auth_providers.py) confirms login_authorize and
associate_authorize share the same dev-JSON / prod-302 behavior across
both providers, so this aligns Google with how Steam already works.
@netlify
Copy link
Copy Markdown

netlify Bot commented May 27, 2026

Deploy Preview for criticalbit-auth-web ready!

Name Link
🔨 Latest commit 29e2b09
🔍 Latest deploy log https://app.netlify.com/projects/criticalbit-auth-web/deploys/6a166bb0dfd1230008640883
😎 Deploy Preview https://deploy-preview-44--criticalbit-auth-web.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@amrtgaber amrtgaber merged commit 7546ba8 into main May 27, 2026
6 checks passed
@amrtgaber amrtgaber deleted the fix/google-oauth-csp branch May 27, 2026 03:59
amrtgaber added a commit that referenced this pull request May 27, 2026
Replaces 6 `window.location.href` calls with TanStack Router's
`useNavigate` across login, register, logout, oauth callback, profile
delete, and associate-complete. No more white flash between auth state
changes.

## The race that wasn't

`login-page.tsx:36-37` previously documented:
> Hard redirect — reloads the page so AuthProvider picks up the new
> cookie cleanly. Client-side navigate has a race condition with the
> onUnauthorized handler.

E2E testing (Playwright against real API + Postgres) shows the race
does NOT manifest with `await auth.checkAuth() + await navigate()`:
React 18's batching plus TanStack Router's context propagation (via
`<RouterProvider context={{ auth }} />` in `main.tsx:105`) means the
updated context is visible by the time `beforeLoad` runs. No
`router.invalidate()` needed.

The pattern by call site:

- **Login / register**: `await auth.checkAuth() → await navigate(...)`.
  Refreshes the AuthContext with the new cookie's identity before the
  destination's beforeLoad checks `isAuthenticated`.
- **Logout / delete**: `await auth.logout() → await navigate("/login")`.
  Clears local state before navigating so /login's "redirect
  authenticated users to /profile" guard doesn't bounce the user back.
- **OAuth-complete**: keeps a direct `api.get("auth/me")` (so the
  routing decision sees current data without waiting for a render)
  PLUS `await auth.checkAuth()` (so the AuthContext is fresh for the
  destination route).
- **Associate-complete**: pure in-app nav with no auth state change.

## Preserved hard-nav cases

- Cross-origin redirects (consumer apps like hera-streamer-…) still use
  `window.location.href` — useNavigate is for in-app routes only.
- Outbound to provider OAuth (Google / Steam authorize) keeps direct
  navigation (CSP + the dev/prod JSON-vs-302 split, fixed in #44).

## Test plan

- [x] `pnpm test:run` — 45 tests pass.
- [x] `pnpm exec tsc -b` clean.
- [x] Live E2E: register → /profile, logout → /login, login → /profile,
  delete → /login. No bounce, no white flash. Backend log confirms
  request chain (register/login/accept-tos/me/connections; logout;
  delete/logout).

Closes #43.
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.

1 participant