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