Cookies, sessions and state

Set-Cookie attributes, Domain, Path, Secure, HttpOnly and SameSite, session identifiers, third-party cookie restrictions, and storage alternatives.

Set-Cookie and its attributes

HTTP is stateless: each request stands alone. Cookies are the mechanism that lets a server recognise the same client across requests, and every protection you get comes from the attributes you attach when you set them.

HTTP/1.1 200 OK
Set-Cookie: sid=8f3c1a...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=604800
Set-Cookie: theme=dark; Path=/; Max-Age=31536000; SameSite=Lax
AttributeWhat it doesLeave it out and
DomainWidens the cookie to a domain and its subdomainsThe cookie is host-only, which is usually what you want
PathLimits which paths the browser sends it toIt defaults to the directory of the request
SecureSend only over HTTPSThe cookie travels in plaintext on the first HTTP request
HttpOnlyHidden from JavaScriptAny XSS can read the session
SameSiteLax, Strict or None cross-site behaviourBrowsers treat it as Lax
Max-Age / ExpiresLifetimeIt is a session cookie, gone when the browser closes
PartitionedScopes the cookie to the embedding siteThird-party cookies are blocked outright
  • A cookie is scoped by host and path, never by port. Two apps on ports 8080 and 8081 of one host share cookies.
  • Cookies are not secret storage: they sit in the browser profile and are attached automatically to matching requests.
  • Deleting a cookie means sending Set-Cookie with the same name, path and domain plus Max-Age=0.

Session identifiers

GET /dashboard HTTP/1.1
Host: app.example.com
Cookie: sid=8f3c1a...; theme=dark
If-None-Match: "v7"
// Node/Express sketch: server-side session in Redis
app.use(session({
  name: 'sid',
  secret: process.env.SESSION_SECRET,
  store: new RedisStore({ client: redis }),
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 1000 * 60 * 60 * 8
  }
}));
  • The cookie carries only an opaque identifier; the data lives server-side, so you can revoke a session by deleting the record.
  • Rotate the identifier on login and on privilege change to prevent session fixation, where an attacker plants a known id beforehand.
  • Enforce both an idle timeout and an absolute one: eight hours of inactivity, or seven days regardless of activity.
  • Logging out must invalidate the server-side record, not merely clear the cookie — a copied cookie would otherwise still work.
⚠️
SameSite=Lax is the browser default and stops most cross-site POSTs, but it is not a CSRF defence on its own. Keep the token-based protection as well, and add Vary: Cookie or a private cache directive so a shared cache can never serve one user's page to another.

Third-party restrictions and alternatives

StoreSent automatically?Accessible to JS?Survives
CookieYes, on matching requestsNo, if HttpOnlyIts expiry, across tabs
localStorageNoYesUntil cleared
sessionStorageNoYesUntil the tab closes
IndexedDBNoYesUntil cleared; suited to larger data
In-memoryNoYesUntil reload
// an in-memory access token avoids XSS readability but dies on reload
let accessToken = null;

export function setToken(t) { accessToken = t; }
export function authHeader() {
  return accessToken ? { Authorization: 'Bearer ' + accessToken } : {};
}

// and refresh through an HttpOnly cookie the JS never sees
async function refresh() {
  const r = await fetch('/auth/refresh', { method: 'POST', credentials: 'include' });
  if (!r.ok) throw new Error('session expired');
  setToken((await r.json()).access_token);
}
  • Third-party cookie blocking breaks embedded widgets that relied on them; same-origin iframes and Partitioned cookies are the replacement patterns.
  • Storing credentials in localStorage turns any XSS into a lasting account takeover — an HttpOnly cookie is strictly better for sessions.
  • A token in memory plus an HttpOnly refresh cookie is the common compromise for single-page apps.

FAQ

Do cookies affect caching?
They can. A response that varies by cookie must not be stored in a shared cache; mark it Cache-Control: private or send Vary: Cookie. Otherwise one user's authenticated page may be served to another.
What size can a cookie be?
About 4 KB per cookie, and browsers limit the number per domain to roughly 50, with older limits on the total. Keep identifiers small and put bulk data in server-side storage.

Authentication on the wire HTTP API design in practice

Last refreshed 2026-09-18.