By Selim Aydin ·

SameSite Cookie Explained: Lax vs Strict vs None

If you have ever stared at a cookie audit and seen a row flagged samesite_default or samesite_none_insecure, you already know the samesite cookie attribute matters more than its three-word name suggests. It answers one question, and only one: when a request leaves your site or arrives from somewhere else, does the browser send this cookie along with it? Everything else, the login that breaks after an OAuth redirect, the widget that logs a user out the moment it is embedded, the yellow warning rows in Chrome DevTools, traces back to that single question.

This is not a 2020 migration story, even though most of the articles still online about it were written for that migration. Chrome finished rolling out its enforcement in August of that year and the flags people used to test it were removed from chrome://flags a year later. What is left is a permanent, ongoing decision every cookie on your site has to make, and the browsers still do not agree on what happens when you skip it.

What "Same-Site" Actually Decides

Before Lax, Strict, or None mean anything, you need a working definition of "site." It is not the same as "origin," and mixing the two up is where most of the confusion starts.

Same-site vs cross-site, in plain terms

A "site" is the registrable domain plus the scheme, checked against the public suffix list. That sounds dense, so here is the practical version: www.example.com and checkout.example.com are the same site. Two different projects hosted on github.io are not, because github.io itself is on the public suffix list.

Port is ignored. Scheme is not: http://example.com and https://example.com count as different sites for SameSite purposes, a rule known as "schemeful same-site" that Chrome shipped in version 86.

That one line is why a plain HTTP-to-HTTPS redirect on your own domain can silently drop a Strict cookie. MDN's Set-Cookie reference lays out the full definition if you want the exact wording, and it is worth a bookmark rather than a memorized summary.

SameSite=Lax: the quiet default in Chrome

Lax sends the cookie on same-site requests, plus one specific category of cross-site request: a top-level navigation, meaning the address bar itself changes, that also uses a safe HTTP method (GET, HEAD, OPTIONS). Click a link from an email or another site, and a Lax cookie rides along. Submit a form with method="POST" from another site, or load your page inside an iframe, or fetch it with JavaScript, and it does not. Images, scripts, and embedded frames never count as top-level navigation, so a Lax cookie is invisible to all three.

This is chrome samesite by default territory: Chromium treats a cookie with no SameSite attribute at all as Lax. Firefox never converged on that behavior, and Safari solves the same problem a completely different way, which we will get to. Nobody's browser silently promotes a cookie to Strict, and none of them silently allow it everywhere either. "Default" is doing a lot of quiet, browser-specific work in that sentence.

The two-minute exception nobody mentions

Here is the detail that separates a careful explainer from a quick one. Chromium gives cookies with no SameSite attribute at all a short grace period on cross-site POST requests: if the cookie is two minutes old or younger, it still goes out, a mode the spec calls "Lax-allowing-unsafe." Chromium's own documentation describes this grace period as a deliberate transition measure built from deployment experience, which is a polite way of saying it exists to stop the rollout from breaking too many login flows at once.

The catch: this exception only applies to cookies with the attribute missing entirely. Set SameSite=Lax explicitly, and you lose the grace period. Explicit Lax is, in this one narrow way, stricter than no attribute at all.

SameSite=Strict: locked to your own site

Strict is the simplest rule of the three and the easiest to get wrong in production. The cookie goes out only on requests that originate from your own site, full stop. Not even the first click from an external link counts, because that click is still a cross-site navigation from the browser's point of view.

Set your session cookie to Strict and a user who clicks a password-reset link from their email arrives on your site looking logged out, even though they were logged in thirty seconds earlier in another tab. That is not a bug in your code. It is Strict doing exactly what its name says.

The support-forum pattern for this is consistent enough to be almost funny if it weren't so common: developers set an OAuth or SSO cookie to Strict, the identity provider redirects back with a 302, and the callback fails because the cookie carrying the state token never showed up. One public example is oauth2-proxy issue #1663, where switching the cookie's SameSite value to strict produced a 403 on the callback for exactly this reason. Strict belongs on cookies tied to sensitive account actions where you are fine asking the user to re-authenticate after arriving from outside, not on the session cookie that everything else depends on.

SameSite=None: everywhere, but only with Secure

None sends the cookie on every request, same-site or cross-site, iframe or top-level navigation, GET or POST. It is what a cookie needs if it has to survive being embedded in someone else's page, carry an identity across an OAuth handoff, or come back after a payment provider bounces a shopper to their site and back. That is the same cross-site territory covered in our guide to third-party cookies, where a cookie set by one domain ends up read back on another entirely.

There is exactly one condition attached: the Secure attribute has to be present too. This is where the samesite none secure pairing comes from, and it is not optional guidance, it is enforced behavior.

RFC 6265bis is explicit about the mechanism: if a cookie's SameSite value is None and Secure is not also set, the browser is instructed to "abort this algorithm and ignore the cookie entirely." Not downgrade it, not warn and continue. Ignore it. If you see samesite_none_insecure in a cookie report, that cookie is not being sent to anyone, in any browser that follows the spec, until Secure gets added.

SameSite Lax vs Strict vs None: what actually changes between them

Written out as a decision matrix, the differences are easier to hold in your head than any prose description:

BehaviorStrictLaxNone
Sent on same-site requestsYesYesYes
Sent on a clicked cross-site link (GET)NoYesYes
Sent on a cross-site form POSTNoNo (2-min exception only when unset)Yes
Sent inside a cross-site iframeNoNoYes
Sent on a cross-site fetch() or image requestNoNoYes
Requires Secure attributeNo (recommended anyway)No (recommended anyway)Yes, enforced
Typical useHigh-sensitivity actionsGeneral session cookiesEmbeds, cross-site SSO, payment returns

Chrome, Firefox and Safari don't agree on the default

This is the part most 2019-era articles get flat wrong, usually by saying "the default is Lax" as if that were a settled, universal fact. It is not. Chromium treats a missing SameSite attribute as Lax, and has since Chrome 80.

Firefox tried the same change, shipped it in Firefox 96, then reverted it for web compatibility reasons; the tracking bug was formally closed as WONTFIX, and Mozilla's own comment on it states there are no plans to revisit the change. Firefox instead treats a missing attribute as None and leans on its own Total Cookie Protection partitioning to contain the risk. Safari takes a third route again: WebKit's Intelligent Tracking Prevention blocks or partitions third-party cookies as a matter of policy, largely independent of whatever SameSite value you set.

Practically, that means "works in Firefox, breaks in Chrome" (or the reverse) is a real, recurring symptom, not a coincidence. It is also why leaving the attribute unset is a gamble rather than a shortcut: you are letting three different vendors decide your cookie's behavior for you, and they have each decided something different.

Picking a value per cookie, not per site

The mistake I see most often in audits is a site-wide policy: someone reads that Strict is "more secure" and sets it on every cookie, then spends a week debugging why login keeps failing after email links and payment redirects. SameSite is not a site setting. It is a per-cookie decision, and a fairly mechanical one once you separate cookies by job.

  • A general session cookie almost always wants Lax. It survives normal navigation, including the first click after a user leaves your site and comes back.
  • A cookie tied to a sensitive, security-relevant action (password change confirmation, admin session) can afford Strict, because you are comfortable asking for re-authentication on arrival from outside.
  • A cookie used by an embedded widget, a cross-site SSO flow, or a payment gateway callback needs None, paired with Secure, no exceptions.
  • If that embed only ever needs to recognize the same top-level site it was set on, and you are on Chrome 114 or later, add the Partitioned attribute alongside None and Secure (the CHIPS mechanism), so the cookie is scoped to the site that embedded it rather than shared across every site that embeds the same widget.

In PHP, that last case looks like: setcookie('sid', $value, ['samesite' => 'None', 'secure' => true, 'httponly' => true]);. If your cookies are added at the server layer through Apache instead of application code, a one-line Header edit Set-Cookie rule can append the attribute without touching the app; htaccess.tools covers writing rules like that if you need the exact syntax.

What a SameSite audit shows you

Chrome moved SameSite warnings out of the console and into the Issues panel back in Chrome 85, and blocked or soon-to-be-blocked cookies show up as yellow rows under Network > Cookies with a tooltip explaining why. That is useful for one site at a time, but it only shows you cookies from the page you are currently inspecting, and it will not tell you which third-party vendor set a cookie you did not expect.

Running the same URL through cookiecheck.tools gives you that view without opening DevTools: it fetches the page once, reads every Set-Cookie header across every redirect hop, and flags cookie samesite problems by name, including samesite_none_insecure for cookies that will be silently dropped, samesite_default for anything left unset, and samesite_none_first_party for a None value on a cookie that never actually leaves your own site. It cannot see cookies written later by JavaScript, so the report also includes a browser-side check that reads what actually landed in the visitor's own cookie jar. Neither view is a compliance verdict; both exist to shorten the gap between "something looks wrong" and "here is the specific cookie and the specific fix."

Does SameSite stop CSRF, or just slow it down

Short version, expanded in the FAQ below: SameSite reduces the CSRF attack surface, it does not close it. OWASP's CSRF Prevention Cheat Sheet is direct about this, describing SameSite as "useful as a defense-in-depth control" that "does not replace a proper CSRF defense in most deployments." The reasons are specific rather than vague: Lax still allows top-level GET navigations through, so an endpoint that changes state on a GET request is still exposed; the site boundary is the whole registrable domain, so an XSS bug on any subdomain can ride past it; and browser support, while good today, was never universal in the way a server-side token is. Keep a synchronizer token, a double-submit cookie pattern, or the newer Sec-Fetch-Site header check as your actual CSRF defense, and let SameSite do the narrower job it is good at.

Frequently Asked Questions

What does SameSite=Lax mean?

Lax means the cookie is sent with same-site requests and with cross-site requests that are both a top-level navigation (the address bar changes) and use a safe method like GET. It is not sent on cross-site POST requests, inside iframes, or on fetch() and image requests originating from another site. Chromium treats a cookie with no SameSite attribute as Lax by default, but explicit Lax loses the two-minute cross-site POST exception that unset cookies get, which makes it marginally stricter in that one edge case than leaving the attribute off entirely.

When do I need SameSite=None?

You need None whenever a cookie has to be sent on a genuinely cross-site request: a widget or login form embedded in someone else's page via iframe, a cookie read during an OAuth or SSO callback that arrives as a cross-site POST, or a cookie a payment provider needs when it redirects a shopper back to your checkout page. If the cookie only ever needs to be present when a user is directly on your own site, None is the wrong choice and Lax will cause fewer surprises.

Why does SameSite=None require Secure?

Because None removes every cross-site restriction on where the cookie gets sent, and Secure is the one remaining guarantee that the cookie only travels over an encrypted HTTPS connection rather than plain HTTP where it could be intercepted. RFC 6265bis makes this a hard requirement at the parsing stage: a cookie declared SameSite=None without Secure is not downgraded to a safer value, it is ignored by the browser entirely, which is why samesite_none_insecure shows up as a rejection rather than a warning in most audit tools.

Does SameSite stop CSRF?

No, not by itself. SameSite reduces CSRF risk as a defense-in-depth measure, but OWASP's CSRF Prevention Cheat Sheet is clear that it does not replace a dedicated CSRF defense. Lax still permits state-changing GET requests through on top-level navigation, the same-site boundary covers an entire registrable domain so a subdomain XSS bug can bypass it, and coverage varies slightly across older or less common browsers. Pair SameSite with a synchronizer token, a double-submit cookie, or a Sec-Fetch-Site check for anything that actually needs CSRF protection.