Cookies over HTTP
Lock down a session cookie
Create a host-bound session cookie that resists script reads, insecure transport, and unnecessary cross-site delivery.
A session cookie carries authority. Whoever holds it is you, as far as the server can tell. So we give it the narrowest access possible.
The cookie
Set authentication cookies on the server, never from JavaScript. This is the field-notes login cookie:
Set-Cookie: __Host-session=opaque-random-id; Path=/; Max-Age=1800; Secure; HttpOnly; SameSite=Lax
Piece by piece:
Secure means HTTPS only. Without it, a plain HTTP request leaks the session to anyone on the network.
HttpOnly means document.cookie and the Cookie Store API can’t read it. Scripts on the page never see the value.
SameSite=Lax means the browser doesn’t attach it to most cross-site requests. A form on another site posting to your server won’t carry the session. More on the other values in the next lesson.
Max-Age=1800 is thirty minutes. Short lifetimes limit the damage of a stolen cookie.
No Domain attribute, so it’s host-only. Only notes.test receives it, not every subdomain.
The __Host- prefix
The name starts with __Host-. That prefix is a rule the browser enforces. It refuses to store the cookie unless it has Secure, has Path=/, and has no Domain attribute.
Why bother, if we set those attributes anyway? Because a misconfigured subdomain or an injected header can’t create a __Host-session cookie that overrides yours. The name itself becomes a guarantee. I use it for every session cookie.
What HttpOnly doesn’t do
HttpOnly stops a script from reading the value. It doesn’t stop a script from using it. Injected JavaScript can call fetch('/api/notes', { method: 'DELETE' }) and the browser attaches the session cookie as usual.
So HttpOnly limits theft, not misuse. You still need to prevent XSS, and you still need CSRF protection for requests that change state. SameSite=Lax covers a lot of that. A token in the request body closes the gap.
Keep authority on the server
The value is opaque-random-id. That’s deliberate. The cookie is a lookup key into a server-side session record. It contains no role, no email, nothing anyone could read or forge.
Put role=admin in a cookie and a user can edit it in DevTools and become admin. Put a signed token in it and you can’t revoke it before it expires. An opaque ID with a server record gives you both.
Rotate and revoke
Issue a new identifier after login and after any privilege change. That defeats session fixation, where an attacker plants a known ID in advance.
On logout, delete the server record. If the cookie was copied, expiring the client copy does nothing. Deleting the record kills every copy.
Take a session cookie from an app you control and check it against this list: prefix, Secure, HttpOnly, SameSite, Domain, Path, lifetime, rotation, revocation. Where you can’t justify one, you found your next fix.
Lesson completed