Cookies

SameSite cookies and CSRF: what None, Lax and Strict really do

The one-line attribute that blocks a whole attack class

Every cookie can carry a SameSite attribute, and it answers a single question: when a request to your site comes from another site, should the browser include this cookie? That sounds niche, but it sits directly under cross-site request forgery — an attack where a page you do not control makes the visitor's browser fire an authenticated request at your site, riding on the session cookie the browser helpfully attaches.

Get SameSite right and a large slice of CSRF stops working before it starts.

The three values

Strict

The browser sends the cookie only when the request originates from your own site. Click a link from an email or another domain into your app, and the cookie is not sent on that first request — so you may appear logged out until you navigate within the site. Maximum protection, occasionally awkward UX.

Lax

The sensible middle, and the modern browser default. The cookie rides along on top-level navigations (a user clicking a link to your site) but not on background cross-site requests like a hidden form POST or an embedded image. This blocks the classic CSRF shapes while keeping normal links working.

None

The cookie is sent on every cross-site request, exactly as cookies behaved years ago. You need this for legitimate cross-site cases — an embedded widget, a cross-domain API. The catch: SameSite=None is only honored together with the Secure flag, so it must be HTTPS-only. A cookie set to None without Secure is rejected by modern browsers.

How this maps to CSRF

CSRF works because the browser automatically attaches your cookies to any request to your domain, even one triggered by an attacker's page. SameSite=Lax or Strict tells the browser to stop attaching the session cookie to those cross-site triggers, which defuses the most common version of the attack.

It is genuinely useful, but it is not the whole story:

  • Lax still allows top-level GET navigations, so any state-changing action must never live on a GET request.
  • Older browsers and odd edge cases exist, so SameSite is a strong layer, not a sole defense.
  • Keep your dedicated CSRF tokens (the anti-forgery token your framework provides) as the primary control, with SameSite as defense-in-depth.

A safe default

For a session or auth cookie, the reliable baseline is SameSite=Lax; Secure; HttpOnly. Use Strict for the most sensitive cookies where the login-from-a-link quirk is acceptable, and reach for None; Secure only when you genuinely have a cross-site use case. The point is to choose deliberately rather than inherit whatever default your framework happened to set.

Check your cookies

You cannot see a cookie's flags by looking at your site — they live in the response headers. An automated scan reads the cookies your site actually sets and reports which are missing SameSite, Secure or HttpOnly. Scan your site and see how your cookies are configured.

Related reading

FAQ

What SameSite value should my session cookie use?
For most apps, SameSite=Lax combined with Secure and HttpOnly is the right default. Use Strict for highly sensitive cookies if you can accept that links from other sites will not be authenticated on the first request.
Does SameSite replace CSRF tokens?
No. SameSite is a strong, free layer that blocks common CSRF shapes, but keep your framework's anti-forgery tokens as the primary defense. Treat SameSite as defense-in-depth, not a complete replacement.
Why is my SameSite=None cookie being ignored?
Modern browsers only honor SameSite=None when the Secure flag is also set, meaning the cookie must be sent over HTTPS. A None cookie without Secure is rejected outright, which is the usual cause.