If you're using Supabase Auth with Next.js App Router and SSR, you might run into this: you're doing a security pass on your app, maybe following an OWASP checklist or prepping for a review, and you decide to harden your cookies by setting httpOnly: true across the board. It's standard advice for session cookies. Every security guide tells you to do it.
For Supabase SSR auth cookies, don't. Here's why.
The setup
Supabase's SSR auth (@supabase/ssr) works by storing session tokens in cookies that are accessible to both the server and the client. The createBrowserClient reads these cookies directly via document.cookie to restore the session without making a network request on every page load.
This is by design — it's how Supabase avoids a round-trip to the auth server on every client-side navigation. The official Next.js setup in the Supabase docs doesn't set httpOnly on these cookies, and that's intentional. But nothing stops you from adding it yourself during a security hardening pass, and there's even bad advice floating around Medium and blog posts that lists "HTTP-only cookies" as a feature of their Supabase auth setup without understanding the tradeoff.
What happens when you add httpOnly
// Applying blanket security hardening...
cookies.set(name, value, {
httpOnly: true, // 🚨 breaks client-side auth
secure: true,
sameSite: 'lax',
})
Server-side rendering still works fine. Middleware works. API routes work. If you're only testing SSR flows, everything looks correct.
But on the client:
-
document.cookiecan't seehttpOnlycookies — that's the entire point of the flag -
createBrowserClienttries to read the session from cookies → finds nothing - Client thinks the user is logged out
- Any client-side auth check fails
- You get redirect loops where middleware says "authenticated" but client components say "not authenticated"
This is confirmed in Supabase's own GitHub discussion — setting httpOnly means onAuthStateChange() and getUser() can't function on the client side.
Why this is hard to catch
It doesn't throw an error. You get:
- Flickers between authenticated and unauthenticated states
- Infinite redirect loops on protected routes
- Hydration mismatches where the server renders a logged-in view but the client immediately redirects to login
- Intermittent failures that depend on whether a page was server-rendered or client-navigated
The symptoms look like a middleware bug, a race condition, or a hydration issue. If you're not specifically looking at your cookie config, you can chase these for hours.
The fix
For Supabase SSR auth cookies specifically, don't set httpOnly:
cookies.set(name, value, {
httpOnly: false, // Required for Supabase client-side session restore
secure: true,
sameSite: 'lax',
path: '/',
})
Or better yet — just don't include httpOnly at all and let it default to false, which is what the official Supabase Next.js guide does.
If you've already set it and you're seeing the symptoms above, this is probably your fix.
"But isn't that insecure?"
Fair question. httpOnly exists to prevent XSS attacks from stealing session cookies via document.cookie. Without it, a successful XSS attack could read your auth tokens.
But Supabase's auth model mitigates this in other ways. The access tokens are short-lived JWTs that automatically rotate. The refresh token is used to get new access tokens, and the PKCE flow (which @supabase/ssr uses by default) adds another layer of protection. You're not storing a long-lived session secret in a naked cookie — you're storing tokens with a built-in expiry lifecycle.
That doesn't mean XSS is a non-issue. It means the right response is to prevent XSS at the application layer (CSP headers, input sanitization, framework protections) rather than to add a cookie flag that breaks your auth library's architecture.
The broader lesson
Security hardening isn't a checklist you apply uniformly. Each flag exists for a specific threat model, and sometimes your auth library's architecture legitimately requires a different tradeoff.
Before blanket-applying cookie policies across your app, read how your auth library actually manages its token lifecycle. For Supabase SSR, the client needs to read those cookies. It's the design.
Top comments (0)