fix(auth): stamp session cookies onto redirect response after OAuth/magic link exchange - #831
Closed
tran-christian wants to merge 10 commits into
Closed
tran-christian wants to merge 10 commits into
tran-christian wants to merge 10 commits into
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.
|
…xchange Next.js does not automatically propagate cookies() mutations into a NextResponse.redirect() response, so the browser never received Set-Cookie headers after exchangeCodeForSession — leaving users perpetually signed out despite Supabase confirming a successful login. Fix: - Collect cookies from setAll() into pendingCookies[] and explicitly apply them to the NextResponse.redirect() before returning it - Handle x-forwarded-host so redirect URLs are correct on Vercel (behind a load balancer where `origin` is the internal host, not the public one) - Validate the `next` param to only allow relative paths - Await signInWithProvider() in LoginDrawer so errors surface via toast instead of being swallowed silently Root cause confirmed via Supabase auth logs: /token returned 200 (login) for every Google OAuth attempt, but users were never signed in — the session tokens were generated but never delivered to the browser.
tran-christian
force-pushed
the
fix/auth-callback-session
branch
from
April 16, 2026 05:48
a9cbef9 to
581b03d
Compare
Contributor
Author
|
Superseded by #832 — clean branch off main with all conflicts resolved and security fixes applied. |
This branch was successfully deployed
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.
Root Cause
Supabase auth logs confirmed: every Google OAuth and magic link attempt returned
/token 200(Supabase-side login succeeded) but users were never signed in on the site. The session tokens were generated but never delivered to the browser.The bug:
setAll()inside thecreateServerClientcallback wrote cookies to Next.js'scookieStore, but Next.js does not automatically propagatecookies()mutations into aNextResponse.redirect()response object. TheSet-Cookieheaders were simply missing from the redirect.Changes
app/auth/callback/route.tspendingCookies[]duringsetAll(), then explicitly stamp them onto theNextResponse.redirect()before returningx-forwarded-host(Vercel): on Vercel the requestoriginis the internal load-balanced host — usex-forwarded-hostin production so the redirect URL is correctnextparam validation: only allow relative paths to prevent open-redirectsrc/components/LoginDrawer.tsxawaitthesignInWithProvider()call so errors surface via the existingnotifyErrortoast instead of being silently swallowedVerification
Supabase auth logs (last 24h): 6+ Google OAuth cycles all showing
/token 200 login— Supabase is working, the cookie delivery is the only gap. All 19 existing tests pass.Notes
The
500: Multiple accounts with the same email addresserror in the logs is a separate issue — logging into GitHub with the same email as a Google account. Fix: Authentication → Providers → Link identities by email in the Supabase dashboard.