Skip to content

Possibly a bug or incomplete support for setting cookies in @supabase/supabase-js v2 when running Next.js 14 App Router with localhost and Node.js 20 development setup. #1396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
conradgann opened this issue Apr 27, 2025 · 7 comments
Labels
bug Something isn't working

Comments

@conradgann
Copy link

conradgann commented Apr 27, 2025

Bug Report — signInWithPassword succeeds but does not set auth cookie (Next.js 14.2.x + Node 20 + localhost)

Bug Description

After successful signInWithPassword, the session is created client-side, but no authentication cookie is set in the browser.
• session object returned correctly.
• document.cookie remains empty immediately after login.
• Browser Application tab shows no auth cookie present.
• SSR middleware (@supabase/ssr) redirects back to /login because no cookie is detected.

Thus, SSR auth is broken even though login appears successful.

Reproduction Steps
1. Next.js 14.2.x app (using App Router /app)
2. Installed @supabase/supabase-js@2.49.4
3. Installed @supabase/ssr
4. Configured standard Supabase client (createClient with public and anon keys)
5. Called signInWithPassword client-side
6. Observed:
• session object successfully returned
• document.cookie remains empty
• Browser does not set any auth cookie
7. Navigated to a protected route
• Middleware sees no cookie, redirects back to /login
• Infinite redirect loop

Expected Behavior

After signInWithPassword, a valid authentication cookie should be automatically set in the browser.
Middleware should recognize the cookie and allow authenticated access to protected routes.

Environment Info
• @supabase/supabase-js: 2.49.4 (also tested 2.40.0, 2.45.x)
• @supabase/ssr: latest
• Node.js: v20.11.1
• Next.js: 14.2.3 and 14.2.4
• Hosting: localhost
• Browsers: Chrome (latest), Safari (latest)

Additional Observations
• Downgrading to @supabase/supabase-js@2.40.0 did not fix the issue.
• LocalStorage does store session data correctly.
• No CSP issues; no custom cookieOptions passed.
• HMR and other Next.js development features work normally.
• Application logs show successful authentication, just no cookie set.
• document.cookie remains empty even after successful login.

Screenshots / Logs
• session object exists after login.
• document.cookie remains empty.
• LocalStorage contains the session token.
• No auth cookie in the browser’s Cookies tab.

Potential Cause

Possibly a bug or incomplete support for setting cookies in @supabase/supabase-js v2 when running Next.js 14 App Router with localhost and Node.js 20 development setup.

Temporary Workaround Considered

Would require manually setting the cookie via an API route based on the session token, but this adds unnecessary complexity compared to the standard client flow.

Minimal Reproduction (ready if needed)

I can quickly reproduce this issue using:

npx create-next-app@latest supabase-auth-bug --typescript --app --eslint --tailwind
cd supabase-auth-bug
npm install @supabase/supabase-js @supabase/ssr

Simple /login page that calls signInWithPassword reproduces the empty document.cookie issue immediately.

Please let me know if you would like me to upload it.

Request

Please investigate if cookie-setting inside @supabase/supabase-js v2 needs adjustment for Next.js 14.2+ App Router architecture under Node.js 20 localhost development.

Thank you!

@conradgann conradgann added the bug Something isn't working label Apr 27, 2025
@MatsTornblom
Copy link

Hi Supabase team,

I can confirm that I am experiencing the exact same issue as described in this bug report.

After a successful signInWithPassword call, the session object is returned correctly client-side, but no sb-auth-token cookie is being set in the browser. Regardless of config, the authentication token is set in local storage, not as cookie. So I get log in to work within the app (as I can use local storage there), but I need it as a cookie as I'm using the token to authenticate in apps I run in subdomains.

Thank you for looking in to this.

@j4w8n
Copy link
Contributor

j4w8n commented Apr 29, 2025

If the session is in local storage, then it sounds like you may have created a supabase client incorrectly.

Can you show the code you're using to do this?

@MatsTornblom
Copy link

MatsTornblom commented Apr 29, 2025

Thank you for your prompt feedback @j4w8n !

I've gotten more and more confused about if the supabase client can actually set the auth tokens as cookies. As you can se from @conradgann above I'm not the only one that expects this, and most codeing AIs share this view too, but perhaps it is a misunderstanding?

Anyway, here is the code I use and that, to my understanding, will set the auth token as a cookie. It doesn't (for me at least), it creates the token i local storage.

import { createClient } from '@supabase/supabase-js';

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || 'https://your-project.supabase.co';
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || 'your-anon-key';

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true,
flowType: 'pkce',
cookieOptions: {
domain: '.[mydomain.com]',
secure: true,
sameSite: 'lax',
path: '/'
}
}
});

The options in "cookieOptions"... kinda lead you to believe a cookie will be set and the purpose of that is to handle subdomain authentication.

If this is a misunderstanding, is there a best practice for how to implement subdomain authentication related to supabase?

I'm really sorry if I'm asking stupid and obvious question :D

@j4w8n
Copy link
Contributor

j4w8n commented Apr 29, 2025

@MatsTornblom that setup will not save the session in cookies.

To have the session saved in cookies, you have to pass a storage configuration to the client. You can create this custom storage yourself or, if applicable, you can use the @supabase/ssr library.

@MatsTornblom
Copy link

MatsTornblom commented Apr 29, 2025

Ah! I see! So simply adding cookieOptions doesn't automatically switch from localStorage to cookies. The cookieOptions only configures how cookies should behave if cookies are used, but doesn't actually enable cookie storage.

I need a custom storage implementation that handles cookies, or use Supabase's SSR library which includes cookie-based storage.

So something like this then, using ssr:

// supabaseClient.js
import { createClient } from '@supabase/supabase-js'
import { createBrowserClient } from '@supabase/ssr'

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY

// Create a Supabase client that uses cookies instead of localStorage
export const supabase = createBrowserClient(
supabaseUrl,
supabaseAnonKey,
{
cookies: {
// Get a cookie by name
get(name) {
return document.cookie
.split('; ')
.find((row) => row.startsWith(${name}=))
?.split('=')[1]
},
// Set a cookie
set(name, value, options) {
document.cookie = ${name}=${value}; path=${options.path}; domain=${options.domain}; max-age=${options.maxAge}; secure; samesite=${options.sameSite}
},
// Remove a cookie
remove(name, options) {
document.cookie = ${name}=; path=${options.path}; domain=${options.domain}; max-age=0; secure; samesite=${options.sameSite}
},
},
cookieOptions: {
domain: '.yourdomain.com', // Replace with your actual domain
secure: true,
sameSite: 'lax',
path: '/'
}
}
)

Will test this.

@j4w8n
Copy link
Contributor

j4w8n commented Apr 29, 2025

Ah! I see! So simply adding cookieOptions doesn't automatically switch from localStorage to cookies. The cookieOptions only configures how cookies should behave if cookies are used, but doesn't actually enable cookie storage.

I need a custom storage implementation that handles cookies, or use Supabase's SSR library which includes cookie-based storage.

So something like this then, using ssr:

// supabaseClient.js
import { createClient } from '@supabase/supabase-js'
import { createBrowserClient } from '@supabase/ssr'

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY

// Create a Supabase client that uses cookies instead of localStorage
export const supabase = createBrowserClient(
supabaseUrl,
supabaseAnonKey,
{
cookies: {
// Get a cookie by name
get(name) {
return document.cookie
.split('; ')
.find((row) => row.startsWith(${name}=))
?.split('=')[1]
},
// Set a cookie
set(name, value, options) {
document.cookie = ${name}=${value}; path=${options.path}; domain=${options.domain}; max-age=${options.maxAge}; secure; samesite=${options.sameSite}
},
// Remove a cookie
remove(name, options) {
document.cookie = ${name}=; path=${options.path}; domain=${options.domain}; max-age=0; secure; samesite=${options.sameSite}
},
},
cookieOptions: {
domain: '.yourdomain.com', // Replace with your actual domain
secure: true,
sameSite: 'lax',
path: '/'
}
}
)

Will test this.

With createBrowserClient it tends to work without passing in custom storage.

Either way, with the ssr library, you do setAll and getAll.

You'd use get, set, remove in your storage implementation if you're not using the ssr library - ie using createClient.

When using the ssr library, you only need createClient when creating a service role, aka admin, client.

@MatsTornblom
Copy link

Ok, thanks so much @j4w8n ! Got it to set cookies seems to be accessible in apps in subdomains. Haven't yet been able to get them to authenticate as the first thing that happens when they try to access the cookie is that is deleted. Not sure why, but it's probably some other problem. Question is if @conradgann who filed the bug report was able to resolve his problem too.

Again, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants