60. Cookies and state
60.1 The mechanics
Server -> client: Set-Cookie: sid=abc; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=3600
Client -> server: Cookie: sid=abc (on every matching request, automatically)
Cookies are attached by the browser, automatically, based on scope — which is precisely why CSRF exists.
60.2 The attributes, and what they actually do
| Attribute | Effect |
|---|---|
Domain=example.com |
Sent to example.com and all subdomains. Omitting Domain is narrower (host-only) and usually safer. |
Path=/app |
Sent only for paths under /app. Not a security boundary (same-origin JS can read across paths). |
Secure |
HTTPS only. Always set it. |
HttpOnly |
Not readable by JavaScript — mitigates XSS token theft |
SameSite=Strict |
Never sent on cross-site requests, including top-level navigation. Breaks "click a link from email and be logged in". |
SameSite=Lax |
Sent on top-level GET navigation only. The modern default. |
SameSite=None |
Sent on all cross-site requests — requires Secure. Needed for third-party embeds/SSO. |
Max-Age / Expires |
Persistent; without either, it's a session cookie |
__Host- prefix |
Browser enforces: Secure, Path=/, and no Domain. The strongest binding available. |
__Secure- prefix |
Browser enforces Secure. |
60.3 Scoping hazards
- Cookies ignore ports and (mostly) scheme. A cookie set by
http://example.com:8080is sent tohttps://example.com:443. Cookies are not origin-scoped like the rest of the web platform. - Subdomain leakage:
Domain=example.comsends your session cookie tountrusted-user-content.example.com. Use host-only cookies, and isolate user content on a different registrable domain, not a subdomain. - Cookie jar size limits (~4 KB per cookie, ~50 per domain) — exceed them and cookies are silently dropped.
- Every cookie is sent on every request to that scope, including images and API calls. Large cookies are a real, measurable performance cost.
60.4 CSRF, and how SameSite changed it
Classic CSRF: an attacker's page issues a form POST to your bank; the browser helpfully attaches the session cookie.
Defences, in order of strength:
SameSite=LaxorStrict— kills most classic CSRF. Now the browser default for cookies without an explicit attribute.- Anti-CSRF token — a random value in the form, checked server-side. Still
required for cross-site flows and for
SameSite=Nonecookies. - Check
Origin/Sec-Fetch-Siteheaders server-side. - Use
Authorization: Bearerinstead of cookies — headers are not attached automatically, so CSRF doesn't apply (but you then own token storage, andlocalStorageis XSS-exposed).
60.5 Real-world example
A SaaS moved its API to api.example.com while the app stayed at
app.example.com, and set session cookies with Domain=example.com so both
would work. Six months later a marketing microsite at
promo.example.com — run by an agency, with a stored-XSS hole — leaked every
logged-in user's session cookie. The fix: host-only __Host- cookies plus a
token exchange for the API, and moving third-party content off the registrable
domain entirely.
Try it yourself
curl -sI https://example.com | grep -i set-cookie
# inspect flags in the browser: DevTools > Application > Cookies
# (columns for Secure, HttpOnly, SameSite are exactly the attributes above)