Headers, Cookies & State
How a stateless protocol fakes memory — and where every auth bug you'll ever hit lives.
35 minDifficulty 2/5api · auth · stateReviewed
Before this
Why this exists
HTTP forgets you between every request. Yet you stay logged in across a dozen page loads. Something is carrying your identity along, invisibly, on every single request — and that same mechanism is behind most authentication bugs, most CORS confusion, and an entire category of security vulnerability.
The mental model
A cloakroom ticket. The venue doesn't remember your coat — you hold a numbered stub, and you show it every time. Lose the stub and anyone holding it can collect your coat, because the ticket is the only thing being checked.
How it works
Headers are metadata, strictly separate from the body
Headers describe the message: what format the body is in, who's asking, what the client can accept, how long the response may be cached. They're key-value pairs, they're case-insensitive, and they're readable by every proxy and cache along the way without touching the body. That separation is deliberate and it's what makes intermediaries useful — a CDN can cache your response without having any idea what's in it.
A cookie is a header the browser manages for you
The server sends `Set-Cookie` in a response. The browser stores it and automatically attaches it as a `Cookie` header on every subsequent request to that domain. That automatic part is the whole point — and simultaneously the whole problem. The browser doesn't ask whether you meant to send it, so a request triggered by a malicious site *also* carries your cookies. That's cross-site request forgery, and it exists purely because cookies are automatic.
The cookie flags are the security model
Four attributes carry nearly all the safety. **HttpOnly** hides the cookie from JavaScript, so an XSS payload can't read your session token. **Secure** sends it only over HTTPS. **SameSite** controls whether it travels on cross-site requests — `Lax` (the modern default) blocks it on cross-site POSTs, killing most CSRF; `Strict` blocks even top-level navigation; `None` allows everything and requires `Secure`. **Max-Age** or **Expires** sets the lifetime. Storing a session token in `localStorage` instead means giving up HttpOnly entirely — any XSS can read it.
The cookie is the credential, so possession is everything
The server doesn't verify who's holding the ticket, only that the ticket is valid. Anyone who obtains the cookie *is* you, as far as the server is concerned. That's why every mitigation is about preventing the cookie from leaking — HttpOnly against XSS, Secure against network sniffing, SameSite against CSRF — and why sessions should be invalidated server-side on logout rather than merely deleting the client's copy.
The mechanism
A realistic session cookie: ``` Set-Cookie: sid=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400 ``` Each attribute is doing a job. Drop `HttpOnly` and one XSS bug hands over every session. Drop `Secure` and the cookie travels in plaintext on any accidental HTTP request. Set `SameSite=None` without understanding it and you've re-enabled CSRF. The final step matters most: logout must delete the session **server-side**. Clearing the browser's copy alone leaves a token that still works if anyone captured it.
sequenceDiagram
participant B as Browser
participant S as Server
B->>S: POST /login (email + password)
S->>S: verify, create session
S-->>B: 200 + Set-Cookie: sid=abc#59; HttpOnly#59; Secure#59; SameSite=Lax
Note over B: stored, scoped to the domain
B->>S: GET /dashboard + Cookie: sid=abc
S->>S: look up session abc
S-->>B: 200 personalised page
B->>S: POST /logout + Cookie: sid=abc
S->>S: DELETE session abc server-side
S-->>B: Set-Cookie: sid=#59; Max-Age=0What people get wrong
- Deleting the cookie logs the user out.
- It removes the browser's copy. The session remains valid server-side until you invalidate it there. Anyone who captured the cookie can keep using it. Logout must destroy the server-side session, or revoke the token.
- localStorage is safer than cookies for tokens because it isn't sent automatically.
- It avoids CSRF but forfeits HttpOnly, so any XSS can read the token directly. You trade a risk with a good mitigation (SameSite) for one with none. An HttpOnly SameSite cookie is generally the safer default.
- Headers are secure because users can't see them.
- Any client fully controls its own request headers. They're trivially forged. Headers are a transport mechanism, not a trust boundary. Only cryptographic verification of the contents makes them trustworthy.
- SameSite=Strict is always the right choice.
- Strict blocks the cookie even on top-level navigation from another site, so following a link into your app appears logged out. Lax is the usual right answer: it blocks cross-site POSTs where CSRF lives, while still allowing normal inbound links to work.
When not to use it
- A public API consumed by mobile apps and servers, where there's no browser to manage cookies.
- An Authorization header with a bearer token. Cookies exist for browser convenience; non-browser clients gain nothing from them.
- You need to store more than a few kilobytes of client state.
- Store the data server-side keyed by session id. Cookies are capped around 4KB and are sent on every single request, so size costs bandwidth continuously.
Terms
- Set-Cookie
- — The response header instructing the browser to store a cookie.
- HttpOnly
- — Makes a cookie invisible to JavaScript, defending against XSS token theft.
- SameSite
- — Controls whether a cookie is attached to cross-site requests. The primary CSRF defence.
- CSRF
- — Tricking a browser into making an authenticated request the user didn't intend, exploiting automatic cookie attachment.
- XSS
- — Injecting script into your page. HttpOnly limits the damage by hiding session cookies from it.
- Bearer token
- — A credential sent in the Authorization header. Whoever bears it is treated as the owner.
In an interview
Where should a session token be stored in a browser, and why?
- an HttpOnly, Secure, SameSite cookie by default
- HttpOnly prevents XSS from reading it; localStorage cannot offer this
- SameSite=Lax handles most CSRF
- localStorage avoids CSRF but trades it for an unmitigable XSS risk
What's the difference between CSRF and XSS?
- CSRF makes the browser send an authenticated request the user didn't intend
- XSS runs attacker script inside your origin
- SameSite and CSRF tokens address CSRF
- output encoding, CSP and HttpOnly limit XSS
- XSS defeats most CSRF defences, so it's the more severe of the two
Can you recall it?
Why are cookies both the solution to HTTP's statelessness and the cause of CSRF?