How HTTP Works

Cookie attribute

< Set-Cookie: id=a3fWa; SameSite=Lax

SameSite Cookie Attribute: Strict, Lax and None

How SameSite works: Strict, Lax and None, what counts as same-site, why Lax-by-default is Chromium behavior, and how to fix cookies blocked cross-site.

Reviewed 4 min readintermediate4 sourcesTry itMarkdown
Default if omitted
Lax in Chromium (Lax-by-default); other browsers vary
Accepts
Strict, Lax or None (None requires Secure)
Sent back to server
No: never echoed in the Cookie header
Spec
RFC 6265bis (draft)
On this page

TL;DR: SameSite decides whether a cookie rides along on requests that start from another site. Set it explicitly: Lax for session cookies, Strict for high-risk ones, None; Secure only for cookies that must work inside third-party embeds. The “unset means Lax” default is Chromium-only.

Same-site is not same-origin

The most common SameSite confusion is the definition of “site”. A site is the scheme plus the registrable domain (eTLD+1, computed with the Public Suffix List). Port and subdomain do not matter.

Request fromToSame-site?
https://app.example.comhttps://api.example.comYes (both example.com)
https://example.com:3000https://example.com:8443Yes (ports ignored)
https://example.comhttps://example.orgNo
http://example.comhttps://example.comNo in Chrome and Edge (schemeful same-site, Chrome 89+); other browsers have not matched this consistently, so do not rely on it
https://alice.github.iohttps://bob.github.ioNo (github.io is on the Public Suffix List)

So a frontend on app.example.com calling api.example.com sends SameSite=Strict cookies fine, while the same frontend calling api.example.net does not, even though CORS is configured correctly. CORS and SameSite are independent checks; see CORS vs CSP and the CORS guide.

The three values

ValueCross-site top-level link (GET)Cross-site POST formfetch / XHR / iframe / img
StrictNot sentNot sentNot sent
LaxSentNot sentNot sent
NoneSentSentSent (requires Secure)
Set-Cookie: session=abc123; SameSite=Lax; Secure; HttpOnly; Path=/

Strict has a UX cost: a user who clicks a link to your site from an email or chat arrives without the cookie, so the first page renders logged out. Typical workaround is a Lax session cookie plus a Strict cookie only for sensitive actions, or a redirect hop that re-requests the page from same-site context.

Browser differences

BehaviorChrome / EdgeFirefoxSafari
Missing SameSite treated as LaxYes (since Chrome 80)NoNo
“Lax+POST” exception for unmarked cookies younger than 2 minutesYesNot applicableNot applicable
SameSite=None requires SecureYes (enforced alongside Lax-by-default)Not enforced by default; the requirement is in RFC 6265bisNot enforced; Safari 12 and macOS 10.14’s Safari 13 treat None as Strict, fixed in 10.15 and later
Third-party cookiesStill allowed by default as of this review; Partitioned (CHIPS) availableBlocked or partitioned by Total Cookie ProtectionBlocked by Intelligent Tracking Prevention

Treat the table as a snapshot: third-party cookie policy has changed repeatedly, and Firefox and Safari restrictions apply even to cookies with SameSite=None. The practical rule is the same everywhere: if your feature needs a cookie in a cross-site iframe, test in all three browsers and plan for Partitioned cookies or the Storage Access API instead.

The Lax+POST exception exists only in Chromium and only for cookies with no explicit SameSite attribute that were set less than two minutes ago. It was a migration aid for SSO flows that POST back to the relying party. Do not depend on it; set SameSite=None; Secure explicitly if you need cross-site POST.

Console messages and what they mean

Browser messageCause
Cookie "x" has been rejected because it is in a cross-site context and its "SameSite" is "Lax" or "Strict" (Firefox)A cross-site request tried to set or send a Lax/Strict cookie
This Set-Cookie was blocked because it had the "SameSite=None" attribute but did not have the "Secure" attribute (Chrome)Add Secure and serve over HTTPS
This attempt to set a cookie via a Set-Cookie header was blocked because it had the "SameSite=Strict" or "SameSite=Lax" attribute but came from a cross-site response (Chrome)Cross-site fetch response set a Lax/Strict cookie; use None; Secure or restructure

Reproduce what the server sends before debugging the browser:

curl -sI https://example.com/login | grep -i '^set-cookie'
// Express: cross-site API called from another registrable domain
res.cookie('session', token, {
  sameSite: 'none',
  secure: true,
  httpOnly: true,
  partitioned: true // CHIPS: separate jar per top-level site; Chromium and Firefox
})
# Django settings.py
SESSION_COOKIE_SAMESITE = 'None'
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SECURE = True
# Flask
app.config.update(SESSION_COOKIE_SAMESITE='None', SESSION_COOKIE_SECURE=True)

On the client the request must also opt in: fetch(url, { credentials: 'include' }), and the server must send Access-Control-Allow-Credentials: true with a specific (non-*) Access-Control-Allow-Origin.

OAuth and SSO redirects

When an identity provider redirects back to your callback, that is a cross-site top-level GET navigation. A Strict session cookie is not sent, so the callback sees no session. Lax works for this flow. If the provider returns the result with a form_post response mode (cross-site POST), even Lax cookies are withheld; the state cookie for that callback needs SameSite=None; Secure with a short Max-Age.

SameSite does not replace CSRF tokens

Lax still allows cross-site GET navigations, and requests from sibling subdomains such as evil.example.com to app.example.com are same-site. Keep CSRF tokens (or Sec-Fetch-Site checks) for state-changing endpoints, and never mutate state on GET.

Frequently asked questions

What is the SameSite cookie attribute?

SameSite tells the browser whether to attach a cookie to requests that start from a different site than the cookie belongs to. It is the main browser-side defense against cross-site request forgery, but it is defense in depth and does not replace CSRF tokens.

What is the difference between SameSite Strict and Lax?

Strict never sends the cookie on any cross-site request, including a top-level navigation from a link on another site. Lax sends it on top-level navigations that use a safe method such as GET, but not on cross-site POST, fetch, XHR, iframe or image requests.

What is the default SameSite value?

Chrome and Edge (Chromium) treat a cookie with no SameSite attribute as Lax. Firefox and Safari do not apply Lax-by-default, so an unmarked cookie behaves as if it were None there, subject to their separate third-party cookie blocking. Always set SameSite explicitly instead of relying on the default.

Why is my SameSite=None cookie rejected?

Browsers that implement the rule reject SameSite=None unless the cookie also has the Secure attribute, and Secure cookies are only accepted from HTTPS pages (or localhost). Chrome logs "This Set-Cookie was blocked because it had the SameSite=None attribute but did not have the Secure attribute".

Is localhost the same site as 127.0.0.1?

No. Hosts are compared by registrable domain, and an IP address or a bare hostname like localhost is its own site, so localhost:3000 and localhost:8080 are same-site (ports are ignored) but localhost and 127.0.0.1 are cross-site.

Sources

  1. MDN Web Docs: Set-Cookie, SameSite attributedeveloper.mozilla.org
  2. RFC 6265bis (draft): SameSite attribute and same-site definitiondatatracker.ietf.org
  3. web.dev: SameSite cookies explainedweb.dev
  4. Chrome Platform Status: SameSite=Lax by defaultchromestatus.com

Keep going

Browse /search