Fundamentals · Networking & storage
Cookies (attributes, SameSite)
⚡ TL;DR — A cookie is a string the browser re-sends on every matching request. The value is trivial; the attributes are the entire security story.
HttpOnlyblocks XSS theft,Secureblocks eavesdropping, andSameSiteis your first-line CSRF defence by controlling whether the cookie rides along on cross-site requests.
🧠 Mental model
Think of a cookie as ambient authority: once set, the browser attaches it automatically to every qualifying request — you don't opt in per fetch, the browser opts you in. That's exactly why cookies power sessions and exactly why they're the vector for CSRF: the attacker's page can trigger a request to your site, and the browser helpfully includes your login cookie.
Each attribute is a knob that narrows when the browser will attach it:
| Attribute | Narrows | Defends against |
|---|---|---|
Secure |
HTTPS only | network sniffing |
HttpOnly |
hidden from document.cookie |
XSS token theft |
SameSite |
cross-site behaviour | CSRF |
Domain/Path |
which URLs match | over-sharing |
Max-Age/Expires |
lifetime | stale sessions |
⚙️ How it actually works
SameSite is the one interviewers dig into. "Site" here means registrable domain (eTLD+1) — a.example.com and b.example.com are the same site but different origins. The three values:
SameSite=Strict— cookie is withheld on all cross-site requests, including top-level navigations. Follow a link from Gmail to your app and you arrive logged out until the next same-site request. Great for CSRF, poor UX for session cookies.SameSite=Lax— the modern default. Cookie is sent on top-level GET navigations (clicking a link) but withheld on cross-site subresource requests and non-GET (form POST,fetch, image pings). This kills most CSRF while keeping "click a link, stay logged in" working.SameSite=None— sent on all cross-site requests; required for third-party cookies (embeds, SSO iframes). Browsers mandateSecurealongside it, or the cookie is rejected.
Since ~2020, browsers treat a cookie with no SameSite as Lax — so the historical CSRF-by-default footgun is largely closed, but you should still set it explicitly.
Cookie prefixes are the underrated hardening layer:
__Secure-prefix → browser rejects the cookie unless it'sSecure.__Host-prefix → requiresSecure, noDomain, andPath=/— meaning it's locked to the exact host and can't be overwritten by a subdomain. The gold standard for session cookies.
💻 Code
A session cookie, hardened — set by the server, not JS:
Set-Cookie: __Host-sid=a1b2c3; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600
Line by line, why each token is there:
__Host- → locked to this exact host; a subdomain can't overwrite it
HttpOnly → document.cookie can't read it; XSS can't exfiltrate the session
Secure → never sent over plain http (also required by __Host- and SameSite=None)
SameSite=Lax → not attached to cross-site POST/fetch → blocks the common CSRF
Path=/ → required by __Host-
Max-Age=3600 → expires in 1h; short-lived sessions limit theft window
A third-party cookie (analytics pixel, embedded widget) has no choice:
Set-Cookie: _track=xyz; Secure; SameSite=None
⚖️ Trade-offs
SameSite=Strictis the most secure and the most annoying. For a bank, worth it. For a consumer app where inbound links must land logged-in,Laxis the pragmatic default — and you back it with a real CSRF defence (token/double-submit) rather than relying onSameSitealone.SameSiteis not a complete CSRF solution. It doesn't cover same-site sub-domains you don't control, and older browsers ignore it. Treat it as defence-in-depth, not the whole wall.HttpOnlybreaks the "read the token in JS" pattern — on purpose. If your SPA needs to attach a bearer token in a header, you're choosing an XSS-readable store.HttpOnlycookies +SameSiteis the more defensible design for session auth.- Big cookies tax every request. Cookies ride on all requests to the domain, including static assets. A bloated cookie inflates request headers and hurts TTFB — serve static assets from a cookieless domain if it's significant.
💣 Gotchas interviewers probe
- "Same site" ≠ "same origin".
SameSitekeys on the registrable domain (eTLD+1), ignoring scheme-until-recently, subdomain, and port. Conflating it with the same-origin policy is the classic slip. SameSite=NonewithoutSecureis silently dropped. People debug a "cookie not setting" bug for an hour over this.- Default is now
Lax, not "none". Saying "cookies are sent everywhere by default" is out of date and a red flag. HttpOnlycookies are invisible todocument.cookie— so you can't debug them there; check DevTools → Application → Cookies. And no, JS cannot delete anHttpOnlycookie.Domain=example.comwidens scope to all subdomains — the opposite of what people expect. OmittingDomain(host-only) is narrower and safer.- A cookie set on
Path=/appisn't sent to/api— path scoping silently drops it, a common "why is my session gone" bug.
🎯 Say this in the interview
"The cookie value barely matters — the attributes are the security model. For a session cookie I set it server-side with
HttpOnlyso XSS can't steal it,Secureso it never crosses plain HTTP, andSameSiteto control cross-site behaviour.SameSite=Laxis my default — the cookie still rides top-level GET navigations so inbound links stay logged in, but it's withheld from cross-site POSTs and fetches, which blocks the common CSRF pattern. If I need maximum protection I goStrict, accepting that users following an external link arrive logged out. For third-party contexts I needSameSite=None, which mandatesSecure. And I'll use the__Host-prefix to pin the cookie to the exact host so a compromised subdomain can't overwrite it.SameSiteis defence-in-depth, though — I still pair it with a CSRF token for anything state-changing."
🔗 Go deeper
- MDN — HTTP cookies — every attribute, prefixes, and the
Set-Cookiegrammar. - web.dev — SameSite cookies explained — the clearest walkthrough of
Lax/Strict/Noneand the default change. - MDN — SameSite attribute — precise semantics, including the
None+Securerequirement. - OWASP — Cross-Site Request Forgery Prevention — why
SameSitealone isn't enough.