Chapter 41 — Cookies: how the web remembers you
In one sentence
HTTP is stateless, so the server hands the browser a small piece of text and the browser sends it back on every matching request — automatically, which is both the feature and the danger.
How they work
Server -> browser:
Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=3600
Browser -> server, on EVERY matching request from then on:
Cookie: session=abc123
The word automatically is the important one. The browser attaches cookies based on scope, without the page asking. That's what makes sessions work — and what makes CSRF possible.
The attributes, and what each actually protects
| Attribute | Effect | Why you want it |
|---|---|---|
Secure |
HTTPS only | Stops the cookie being sent in the clear. Always set it. |
HttpOnly |
JavaScript can't read it | If your site has an XSS bug, the attacker still can't steal the session token |
SameSite=Lax |
Not sent on cross-site requests, except top-level navigation | Blocks most CSRF. This is the modern browser default. |
SameSite=Strict |
Never sent cross-site at all | Stronger, but breaks "click the link in the email and be logged in" |
SameSite=None |
Sent everywhere — requires Secure |
Only for genuine third-party use (embeds, SSO) |
Domain=example.com |
Sent to that domain and all its subdomains | Omitting Domain is narrower and safer |
Path=/app |
Only for URLs under that path | Convenience, not security |
Max-Age / Expires |
Survives browser restart | Without either, it's gone when the browser closes |
__Host- name prefix |
Browser enforces Secure, Path=/, and no Domain |
The strongest available binding |
The trap: cookies are not origin-scoped
Everything else on the web is scoped to an origin (scheme://host:port).
Cookies are not:
Set by: http://example.com:8080
Sent to: https://example.com:443 <-- yes, really.
Cookies ignore the PORT, and (mostly) the SCHEME.
And the Domain attribute is worse:
Set-Cookie: session=abc; Domain=example.com
-> sent to app.example.com (intended)
-> sent to blog.example.com (probably fine)
-> sent to promo.example.com (run by an agency... with an XSS bug)
Rule: don't set Domain unless you truly need subdomain sharing, and never
host untrusted content on a subdomain of your main site.
CSRF, and why SameSite changed everything
You are logged into your bank (cookie stored).
You visit evil.com, which contains:
<form action="https://bank.com/transfer" method="POST">
<input name="to" value="attacker"><input name="amount" value="1000">
</form>
<script>document.forms[0].submit()</script>
The browser HELPFULLY attaches your bank cookie. The bank sees a
perfectly authenticated request.
Defences, strongest first:
SameSite=LaxorStrict— the browser simply doesn't send the cookie. Now the default, which killed most classic CSRF overnight.- Anti-CSRF token — a random value in the form, verified server-side. Still
needed for
SameSite=Noneflows. - Check the
Originheader server-side on state-changing requests. - Use
Authorization: Bearerinstead of cookies — headers aren't attached automatically, so CSRF doesn't apply. (But you then have to store the token somewhere, andlocalStorageis readable by XSS. Pick your trade-off.)
Practice (20 min)
A. See cookies being set:
curl -sI https://github.com | grep -i set-cookie
curl -sI https://example.com | grep -i set-cookie
B. Inspect them properly in the browser: DevTools → Application →
Cookies. The columns are literally the attributes above. Look at a site you're
logged into: is the session cookie Secure? HttpOnly? What's its SameSite?
C. Watch a cookie round-trip:
curl -s -c /tmp/jar.txt https://httpbin.org/cookies/set?flavour=chocolate > /dev/null
cat /tmp/jar.txt # the stored cookie
curl -s -b /tmp/jar.txt https://httpbin.org/cookies # sent back automatically
D. Measure the cost. Cookies are sent on every request to that scope — including images and API calls. Check the size:
curl -sI https://<a site you use> | grep -i set-cookie | wc -c
Multiply by every request on a page load. Large cookies are a real performance problem, which is why static assets are often served from a cookie-less domain.
Common confusions
- "Cookies are insecure." Cookies with
Secure,HttpOnly, andSameSiteare a solid session mechanism — often safer than tokens inlocalStorage, which JavaScript (and therefore XSS) can read. - "
Pathprotects my cookie." It doesn't. JavaScript on the same origin can read cookies across paths. - "Third-party cookies are being removed — does that break sessions?" No. Your own site's cookies (first-party) are unaffected. It's cross-site tracking cookies that are going away.
Check yourself
- Why does the browser send cookies automatically, and what problem does that create?
- What does
HttpOnlyprotect against? What aboutSameSite? - Why is omitting the
Domainattribute safer than setting it? - Name two defences against CSRF.
Further reading
- Docs: MDN, "Using HTTP cookies" and "Set-Cookie" — accurate and complete.
- Article: "SameSite cookies explained" — web.dev. Clear, with diagrams.
- Article: OWASP "Cross-Site Request Forgery Prevention Cheat Sheet".
- Labs: PortSwigger Web Security Academy — free CSRF labs you can actually exploit.