fix(auth): resolve PKCE code_verifier loss during Google OAuth flow - #830
Merged
Conversation
Without a root middleware.ts, Supabase SSR never refreshes the session cookie on the server. This causes auth state to silently break after the JWT expires (~1 hour) on server-rendered routes and the auth callback. The updateSession helper already existed in src/lib/supabase/middleware.ts but was never wired up as a Next.js middleware. Matcher excludes static assets and images to avoid unnecessary overhead.
Next.js 16.0.0 deprecated middleware.ts in favor of proxy.ts, with the exported function renamed from `middleware` to `proxy`. The old file was being silently ignored on Vercel, which is why session refresh never ran and auth broke. Ref: https://nextjs.org/docs/messages/middleware-to-proxy
Matches Next.js 16 naming convention where the root proxy.ts now imports from '@/lib/supabase/proxy' instead of '@/lib/supabase/middleware'. Updates index.ts barrel export accordingly.
Replaces supabase.auth.getUser() with supabase.auth.getClaims() in the session refresh proxy. getUser() makes a network round-trip on every request and can cause random sign-outs in SSR; getClaims() reads JWT claims locally and is the correct method for the proxy layer per Supabase SSR docs. Also adds .env*.local to .gitignore.
Exclude /auth/callback from SSR middleware matcher so session-refresh logic cannot clear the code_verifier cookie before exchangeCodeForSession runs. Revert getClaims() to conditional getUser() — only fires when a session token cookie is present, avoiding an unconditional auth-service round-trip for anonymous traffic. Remove non-standard data?.session guard in callback route. Narrow error-path cookie cleanup to only expire the PKCE code_verifier cookie, preserving valid sessions on transient errors. Add proxy unit tests covering conditional getUser() behaviour.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
tran-christian
added a commit
that referenced
this pull request
Apr 17, 2026
…st handling Replaces the third-attempt manual Set-Cookie stamping with Supabase's canonical @supabase/ssr App Router pattern and adds x-forwarded-host handling — the single constraint from the official example that has never been applied across #830, #832, or the current staged attempt. Root cause: on Vercel, new URL(request.url).origin can resolve to an internal load-balancer host rather than the public domain. When the redirect lands on a different origin than the one @supabase/ssr stamped Set-Cookie on, the browser appears to have lost the session. Changes: - Use createClient from @/lib/supabase/server (already canonical) instead of inlining createServerClient with a custom setAll - Drop pendingCookies[] / response.headers.append('Set-Cookie', ...) and the toSetCookieHeader() helper — Next.js propagates cookies().set() mutations onto the response on its own - Prefer x-forwarded-host in production redirects (canonical Supabase Next.js App Router example) - Keep /auth/callback proxy exclusion (from #830) so session-refresh does not clear the code_verifier - Keep a single _fh diagnostic param to confirm which redirect branch ran
This was referenced Apr 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/auth/callbackfrom SSR middleware matcher so the session-refresh middleware never runs on the OAuth callback path and cannot clear the PKCEcode_verifiercookie beforeexchangeCodeForSessioncompletesgetUser()in proxy — only fires when a session token cookie (sb-{ref}-auth-tokenor chunked variants) is present; anonymous traffic makes zero auth-service round-trips, reducing blast radius of Supabase outagesdata?.sessionguard from callback route — non-standard check that blocked valid sessions*-auth-token-code-verifiercookie on failure, preserving any valid existing session when a transient Supabase error occurs during the exchangegetUser()logic (no session, code-verifier-only, full token, chunked token)Root cause
Two issues combined to break Google OAuth sign-in:
The SSR middleware (
proxy.ts) was running on the/auth/callbackpath. When users had stale session cookies,getUser()triggered arefresh_token_not_founderror, causing the Supabase SDK to callsetAllto clear session state — which inadvertently wiped the freshcode_verifiercookie beforeexchangeCodeForSessioncould read it.getClaims()(commit537c139) does not contact the auth server and therefore never refreshes expired sessions server-side. Reverted togetUser(), now guarded behind a session-cookie presence check.Test plan
sb-*cookies from a previous session can sign in on the next attempt (PKCE verifier cleared on error; valid session preserved)pnpm jest src/lib/supabase/__tests__/proxy.test.ts— 5 tests passing