SameSite Cookies
Tokens (Chapters 5–6) defend by adding a secret to the request. SameSite attacks CSRF from a different angle — precondition #3 (Chapter 1): it tells the browser not to send the cookie cross-site in the first place. It's a one-line cookie attribute that, combined with a 2020 change to browser defaults, reshaped the entire CSRF landscape.
The Attribute
SameSite is set on a cookie and controls whether the browser attaches it to cross-site requests. It has three values:
| Value | Cookie sent on same-site requests? | Sent on cross-site requests? |
|---|---|---|
| Strict | Yes | Never — not even top-level navigation |
| Lax | Yes | Only on top-level GET navigations (clicking a link) |
| None | Yes | Yes (legacy behaviour) — requires Secure |
How Each Value Affects CSRF
- Strict — the cookie is never sent on any cross-site request, so forged requests arrive with no session cookie at all → CSRF fully blocked. The cost: even a legitimate inbound link from another site (e.g. an email link to your dashboard) arrives logged-out, which hurts UX.
- Lax — the cookie is sent on top-level GET navigations (so following a link to your site keeps you logged in) but not on cross-site POSTs, forms targeting your site, or background subresource requests. This blocks the classic POST-form CSRF while preserving normal link UX — the sweet spot, and now the default.
- None — the old behaviour: cookie sent on all cross-site requests. This is what CSRF exploited. Now requires
Secure, and is only appropriate for cookies that genuinely need cross-site use (e.g. third-party embeds, SSO).
SameSite attribute defaulted to None (send everywhere) — which is why CSRF was so easy. In 2020, Chrome (then other browsers) changed the default for cookies without an explicit SameSite to Lax. Overnight, the most common CSRF vector — a cross-site POST form — stopped carrying the session cookie by default, on most of the web, with no developer action. This is why CSRF is less dangerous today than a decade ago. But "less" is not "gone," and relying on the default alone is risky (see below).
Why Lax Stops the Classic Attack
Recall the canonical attack (Chapter 3): an auto-submitting hidden POST form on evil.com. With SameSite=Lax, the browser will not attach the session cookie to that cross-site POST, because Lax only sends cookies on top-level GET navigations — not POSTs, not subresource requests. The forged POST arrives unauthenticated, and the server treats it as a logged-out request. The hidden-form workhorse of CSRF is neutralized by a single cookie attribute.
a.example.com attacking b.example.com is "same-site" and not blocked. (5) Older browsers may ignore it. So use SameSite and tokens — defence in depth (Chapter 8).
"Site" vs "Origin" — a Crucial Distinction
SameSite operates on the concept of site (the registrable domain, e.g. example.com), not origin (scheme + host + port). This is looser than the Same-Origin Policy:
| Request from → to | Same-origin? | Same-site? |
|---|---|---|
app.example.com → app.example.com | Yes | Yes |
blog.example.com → app.example.com | No | Yes (same registrable domain) |
evil.com → app.example.com | No | No |
The middle row is the catch: a sibling subdomain is same-site, so SameSite won't stop a malicious or compromised subdomain — which ties directly to the cookie-injection concern from Chapter 6. SameSite defends the cross-site boundary, not the cross-origin-but-same-site one.
Practical Recommendation
For a typical session cookie: SameSite=Lax (the default, but set it explicitly), plus Secure and HttpOnly. Use Strict for the highest-sensitivity cookies where the "arrive logged out from external links" cost is acceptable (or pair Strict with a Lax "read" cookie). Reserve None for cookies that truly require cross-site sending, always with Secure. And — the recurring theme — layer it with anti-CSRF tokens rather than relying on SameSite alone.
Hands-On Exercises
In DevTools (Application → Cookies), inspect the SameSite value of cookies on a few sites. Then explain, for a SameSite=Lax session cookie, exactly which of these carry it: (a) clicking a link from another site, (b) a cross-site auto-submitting POST form, (c) a cross-site <img> GET.
Explain how the 2020 change of the default SameSite value (None → Lax) reduced CSRF risk across the web without developers doing anything. Then give two reasons you should still set SameSite explicitly and still use tokens.
📄 View solutionA developer sets SameSite=Lax and declares the app "CSRF-proof." Identify the gaps that leave it still attackable — covering state-changing GETs, the site-vs-origin distinction (sibling subdomains), and SameSite=None cookies — and state what to add to actually close them.
Chapter 7 Quick Reference
- SameSite — cookie attribute controlling whether the cookie is sent on cross-site requests (attacks CSRF precondition #3)
- Strict — never sent cross-site (blocks CSRF fully; external links arrive logged-out)
- Lax — sent only on top-level GET navigations; blocks cross-site POST forms while keeping link UX (the default & sweet spot)
- None — sent on all cross-site requests (legacy CSRF-enabling behaviour); now requires Secure
- 2020 default flip (None → Lax) hugely reduced CSRF across the web automatically — but "less" ≠ "gone"
- Gaps: state-changing GETs · Lax+POST grace window · SameSite=None cookies · sibling subdomains · old browsers
- Site ≠ origin — SameSite uses the registrable domain, so
a.example.com→b.example.comis "same-site" and unprotected - Recommendation: session cookie = Lax + Secure + HttpOnly (Strict for high-sensitivity), and tokens — defence in depth
- Next chapter: defence in depth — custom headers, Origin/Referer checks, re-authentication, combining layers