devkult_
tools29converters34
home/blog/samesite-cookies-explained
securitycookieshttp

SameSite Cookies Explained: Strict, Lax and None

The SameSite attribute on a cookie decides whether the browser sends it on requests that come from another site. It's the main reason cross-site request forgery (CSRF) is much harder than it used to be — and the reason some logins, payment redirects and embedded widgets mysteriously "forget" who you are.

Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax

"Site" is not "origin"

SameSite works on sites, which are broader than origins. A site is the scheme plus the registrable domain: https://app.example.com and https://api.example.com are different origins but the same site (https://example.com). A request between them is same-site, so SameSite doesn't restrict it.

https://example.com and https://example.org are different sites. So are http://example.com and https://example.com — browsers now treat the scheme as part of the site.

The three values

Value Sent on cross-site requests?
Strict Never. Not even when the user clicks a link to your site from elsewhere.
Lax Only on top-level navigations with a safe method — following a link, typing the URL. Not on cross-site POSTs, iframes, images or fetches.
None Always. Requires Secure.

What that means for common requests arriving from another site:

Request from another site Strict Lax None
User clicks a link to your page ✗ ✓ ✓
Form POST to your site ✗ ✗ ✓
Your page in an <iframe> ✗ ✗ ✓
<img>, <script> or fetch() to your site ✗ ✗ ✓

The default is Lax — in some browsers

Chrome and Edge treat a cookie without a SameSite attribute as Lax. Firefox and Safari don't apply that default, so the same cookie behaves differently across browsers. Always set SameSite explicitly.

Chrome also has a small exception for cookies with no attribute: for the first two minutes after they're set, they're still sent on cross-site top-level POSTs, so older single sign-on flows keep working. Cookies that say SameSite=Lax explicitly don't get that grace period.

Which one to use

  • Session cookies for a normal website: Lax. Users who follow a link to you arrive logged in, and cross-site POSTs — the classic CSRF vector — arrive without the cookie.
  • Sensitive actions (banking, admin): Strict, often on a second cookie. The trade-off: users clicking a link from email or chat land on your site looking logged out until the next navigation.
  • Cookies that must work cross-site — embedded widgets, third-party integrations: None; Secure. Expect browsers to restrict these further: Safari blocks third-party cookies by default and Firefox partitions them per site.

Why logins break: the cross-site POST

Some sign-in providers and payment pages send the user back to your site with a form POST (OAuth's response_mode=form_post, Sign in with Apple, many 3-D Secure flows). That's a cross-site POST, so a Lax or Strict cookie holding the login state isn't sent, and your callback can't find the session.

Fixes, in order of preference:

  1. Use a redirect (GET) callback if the provider supports it.
  2. Mark only the short-lived state cookie used by the callback as SameSite=None; Secure, and keep the session cookie Lax.

SameSite and CSRF

Lax stops most CSRF, because a forged form on another site can't make the browser send your cookie with a POST. It isn't complete protection:

  • Same-site isn't same-origin. A compromised or user-controlled subdomain (evil.example.com) is same-site and can still send cookie-bearing requests.
  • GET requests that change state are still exposed to top-level navigations. Don't change data on GET.
  • Older browsers ignore the attribute.

Keep CSRF tokens or check the Origin header on state-changing requests, and treat SameSite as a strong second layer.

Debugging

In Chrome DevTools, open Application → Cookies to see each cookie's SameSite value, and the Network tab's Cookies view for a request: blocked cookies are shown with the reason, such as "this cookie was blocked because it had the SameSite=Lax attribute and the request was made from a different site".

For every cookie attribute — HttpOnly, Secure, Max-Age, Domain, Path — see the Set-Cookie reference; for sending cookies on cross-origin fetches, see Access-Control-Allow-Credentials.

More from the blog