-
Notifications
You must be signed in to change notification settings - Fork 578
Description
Describe the bug
Summary
After upgrading @supabase/supabase-js from 2.90.1 to 2.91.0, OAuth login stopped working (both localhost and Vercel). OTP auth continues to work.
Both OAuth providers tested (Google + Discord) are affected.
The regression appears to be caused by the v2.91.0 change “auth: defer subscriber notification in exchangeCodeForSession to prevent deadlock” (PR #2014).
In Next.js Route Handlers / serverless runtimes using @supabase/ssr, OAuth callbacks commonly rely on the SIGNED_IN event emitted during auth.exchangeCodeForSession() to write auth cookies via the SSR cookie adapter. In v2.91.0, SIGNED_IN is now notified via setTimeout(..., 0), which may not run before the request completes, resulting in no auth cookies being set.
This may be an intentional tradeoff to avoid a deadlock in some environments, but it is a breaking behavior change for serverless/SSR callback handlers that expect exchangeCodeForSession to complete cookie persistence before the response returns.
Affected flow
auth.signInWithOAuth()redirects the user to the provider- Provider redirects back to
/auth/callback?code=... - Server route calls
auth.exchangeCodeForSession(code) - Expected: auth cookies are set and user is signed in
- Actual (v2.91.0): callback redirects, but cookies are not set; user remains logged out
In apps that protect pages via Next.js middleware (e.g. calling supabase.auth.getClaims() / getUser() on each request), the next navigation to a protected page (like /dashboard) immediately redirects back to /login because no session is present.
Regression
- ✅ Works:
@supabase/[email protected] - ❌ Broken:
@supabase/[email protected]
Observed result
- Callback handler executes and redirects.
exchangeCodeForSessiondoes not return an error.- No Supabase auth cookies are persisted (e.g.
sb-...cookies). - Subsequent server/client calls return no session; protected routes redirect to
/login.
Expected result
exchangeCodeForSessioncauses the SSR client to persist auth cookies during the callback request.
Suspected cause
v2.91.0 includes:
- Release note: https://github.com/supabase/supabase-js/releases/tag/v2.91.0
- PR: fix(auth): defer subscriber notification in exchangeCodeForSession to prevent deadlock #2014
The compare view shows this change in packages/core/auth-js/src/GoTrueClient.ts inside exchangeCodeForSession:
// previous behavior (v2.90.1)
await this._notifyAllSubscribers('SIGNED_IN', data.session)
// new behavior (v2.91.0)
setTimeout(async () => {
await this._notifyAllSubscribers('SIGNED_IN', data.session)
}, 0)In serverless/route handler environments, cookie-writing (via @supabase/ssr’s subscription to auth events) may require the notification to happen within the lifetime of the request.
Environment
- Next.js: App Router (observed in both localhost dev and Vercel)
@supabase/ssr:0.8.0@supabase/supabase-js:2.91.0(regression),2.90.1(works)- Node:
24.x
Workaround
Pin @supabase/supabase-js to 2.90.1, or insert a macrotask delay after exchangeCodeForSession so the deferred callback runs before returning the response.
This workaround has been verified to restore OAuth sign-in for both Google and Discord in this Next.js app.
await supabase.auth.exchangeCodeForSession(code)
await new Promise((r) => setTimeout(r, 0))Request
Please confirm whether deferring SIGNED_IN notifications in exchangeCodeForSession is intended for SSR/serverless contexts, and if so, consider documenting it as a breaking behavior for SSR OAuth callbacks.
If not intended, possible fixes could include:
- Use a microtask (
queueMicrotask/Promise.resolve().then(...)) instead ofsetTimeout, so it runs before the response returns. - Only defer notifications in browser contexts.
- Provide an option to keep notifications synchronous for SSR.
- Ensure
exchangeCodeForSessionpersists cookie/session state without relying on subscriber timing.
Library affected
supabase-js
Reproduction
No response
Steps to reproduce
Reproduction (Next.js App Router)
- Create a Next.js App Router project.
- Create a server Supabase client using
@supabase/ssrand a cookie adapter (cookies().getAll()/cookies().set()pattern). - Implement a callback route handler that calls
exchangeCodeForSession.
Minimal callback route (similar to what many Next.js + Supabase guides recommend):
// app/auth/callback/route.ts
import { NextResponse } from 'next/server'
import { createClient } from '@/supabase/server'
export async function GET(req: Request) {
const url = new URL(req.url)
const code = url.searchParams.get('code')
if (code) {
const supabase = await createClient()
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (error) return NextResponse.redirect(new URL('/login?error=oauth', url.origin))
}
return NextResponse.redirect(new URL('/dashboard', url.origin))
}- Upgrade
@supabase/supabase-jsfrom2.90.1to2.91.0. - Perform an OAuth sign-in (e.g. Google).
System Info
System:
OS: macOS 26.2
CPU: (10) arm64 Apple M1 Pro
Memory: 200.19 MB / 32.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 22.21.0 - /Users/matt/.nvm/versions/node/v22.21.0/bin/node
Yarn: 1.22.22 - /Users/matt/Documents/GitHub/korean-vocabulary-app/node_modules/.bin/yarn
npm: 10.9.4 - /Users/matt/Documents/GitHub/korean-vocabulary-app/node_modules/.bin/npm
bun: 1.3.5 - /Users/matt/.bun/bin/bun
Deno: 2.5.2 - /opt/homebrew/bin/deno
Browsers:
Chrome: 143.0.7499.193
Safari: 26.2
npmPackages:
@supabase/mcp-server-supabase: ^0.6.1 => 0.6.1
@supabase/ssr: ^0.8.0 => 0.8.0
@supabase/supabase-js: 2.91.0 => 2.91.0Used Package Manager
bun
Logs
No response
Validations
- Follow our Code of Conduct
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Make sure this is a Supabase JS Library issue and not an issue with the Supabase platform. If it's a Supabase platform related bug, it should likely be reported to supabase/supabase instead.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- The provided reproduction is a minimal reproducible example of the bug.