Browser security boundaries

Protect session cookies

Use restrictive cookie attributes and understand why a cookie can be sent even when browser JavaScript cannot read it.

A session cookie carries authority. Whoever holds it is treated as the logged-in user until it expires. So the attributes you set on it decide how far that authority can leak.

HttpOnly keeps the cookie away from document.cookie, which blocks a script from reading it. But HttpOnly does not stop the browser from attaching the cookie to requests. Reading and sending are separate concerns, and each attribute controls a different one.

Set the attributes deliberately

Here is a session cookie set from an Express-style handler, with each attribute chosen on purpose.

res.cookie('session', sessionId, {
  httpOnly: true,          // no script access
  secure: true,            // only sent over HTTPS
  sameSite: 'lax',         // not sent on cross-site subrequests
  path: '/',               // scope to what the app needs
  maxAge: 1000 * 60 * 60,  // 1 hour
})

Notice there is no Domain attribute. Leaving it out scopes the cookie to the exact host that set it. Adding Domain=.flaviocopes.com would widen it to every subdomain, which is authority you rarely want to hand out.

When the rules fit, a __Host- prefix is the strongest baseline the browser enforces for you.

Set-Cookie: __Host-session=abc123; Secure; HttpOnly; SameSite=Lax; Path=/

The __Host- prefix requires Secure, Path=/, and no Domain, so the browser rejects the cookie if any of those is missing. That turns a policy into something the browser checks.

A session cookie is HttpOnly but lacks Secure, so a mistaken HTTP link can expose it before the redirect. Another cookie uses Domain=.flaviocopes.com, giving every subdomain unnecessary scope.

A strict SameSite value can break a legitimate external login return. Choose the narrowest setting that still supports the real flow, then test that flow.

Capture the session Set-Cookie header and justify Secure, HttpOnly, SameSite, Path, Domain, and expiry. Test an HTTP request, a cross-site form, and the real sign-in return path.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →