# Can auth cookies be HttpOnly?

> Not with the standard @supabase/ssr setup, because browser code needs the session. When an HttpOnly, server-only setup makes sense, what it costs, and better ways to reduce XSS risk.

Source: https://supahost.app/kb/httponly-cookies · Updated 2026-11-11

Not with the standard `@supabase/ssr` setup. The browser client reads the session cookie to attach the access token to requests and to refresh it before it expires. An `HttpOnly` cookie can't be read by JavaScript, so the browser client would act as if nobody is signed in. You can use HttpOnly cookies only if the browser never talks to Supabase directly and every call goes through your server.

## Why the cookie is readable

With `@supabase/ssr`, the session (access token and refresh token) is in cookies that both your server and the browser client use:

- the browser client sends the access token with Data API, Storage, Realtime and Edge Function calls
- either side can refresh the session and write the new cookies

That's what lets you query from Client Components, subscribe to Realtime and upload files straight from the browser.

## What HttpOnly would and wouldn't protect

HttpOnly stops a cross-site scripting (XSS) bug from reading the token. It doesn't stop that script from acting as the user: injected code on your page can still call your server routes, and the browser adds the cookies for it. HttpOnly narrows the damage, but the defence that matters is preventing XSS in the first place.

## If you still want server-only cookies

Force the flag in your server client's `setAll`, and route every Supabase call through server code:

```ts
// lib/supabase/server.ts
cookies: {
  getAll: () => cookieStore.getAll(),
  setAll(cookiesToSet) {
    try {
      cookiesToSet.forEach(({ name, value, options }) =>
        cookieStore.set(name, value, { ...options, httpOnly: true, secure: true, sameSite: "lax" }),
      );
    } catch {
      // Called from a Server Component; the proxy writes the cookies.
    }
  },
},
```

Do the same in your proxy or middleware. What you give up:

- No `createBrowserClient`. Sign-in, sign-out and data access all run in Server Actions or Route Handlers.
- Realtime needs a token in the browser, so you'd have to hand one over anyway, which defeats the point, or skip user-scoped Realtime.
- File uploads go through your server instead of directly to Storage.

## Better ways to reduce risk

1. **Set a Content Security Policy** that blocks inline and third-party scripts you don't need.
2. **Shorten the access token lifetime** if a stolen token worries you. On SupaHost: Dashboard → your project → **Auth** tab → **Sign-in & security** → **Access token lifetime** (300 to 604,800 seconds, default 3,600).
3. **Revoke sessions when something looks wrong:** `await supabase.auth.signOut({ scope: "global" })` ends the user's sessions on every device. Access tokens already issued stay valid until they expire.
4. **Keep the `service_role` key on the server only.** It never belongs in a cookie or in browser code.

## Related

- [Server-side auth and SSR](/docs/auth/server-side)
- [Should I shorten cookie Max-Age?](/kb/cookie-max-age)
- [Sessions, JWTs and refresh tokens](/docs/auth/sessions)
- [Keeping keys safe](/docs/security/api-key-safety)
