Browsers changed the default for cookies with no SameSite attribute from 'send everywhere' to Lax, and began rejecting `SameSite=None` without `Secure`. What broke as a result, and how do you handle it?
answer
- absent SameSite now == Lax
- None without Secure = dropped
- form_post SSO callback is a cross-site POST
- fix: None;Secure, or switch to redirect GET
- Lax+POST 2-minute grace window is gone
basics
~20 sCookies with no SameSite now behave as Lax, so anything relying on cross-site delivery broke: embedded iframes, cross-site POST callbacks, SSO and payment redirects that POST back, and third-party widgets. The fix is to mark genuinely cross-site cookies SameSite=None; Secure — and to set SameSite explicitly everywhere instead of relying on defaults.
solid answer
~60 sThe shift makes an omitted `SameSite` mean `Lax`, so cookies silently stop being sent on cross-site iframes, images, `fetch`, and — the painful one — **cross-site POST navigations**. What typically broke: - **SAML and OIDC form_post responses**: the IdP POSTs back to your callback, which is cross-site, so a pre-existing state cookie is not sent and the flow fails with "invalid state". - **Payment gateway returns** that POST the result back to the merchant. - **Embedded widgets and iframes** — chat, help, dashboards inside a partner portal. - **Cross-site `fetch` with `credentials: 'include'`.** The fixes: mark cookies that genuinely must travel cross-site as `SameSite=None; Secure` — which also forces HTTPS end to end; keep the state cookie for `form_post` flows as `None; Secure` or switch the response mode to a redirect GET that Lax permits; and never rely on the default — set the attribute explicitly so behaviour is the same across browser versions. Note `None` is not future-proof: browsers block or partition third-party cookies, so embedded cases may additionally need `Partitioned` (CHIPS) or a redesign.
code
http · 1 lineSet-Cookie: oidc_state=xyz; Path=/callback; Max-Age=300; Secure; HttpOnly; SameSite=Nonego deeper
Know that an absent SameSite now behaves as Lax and that SameSite=None needs Secure; give one example of a broken flow.
Name concrete breakages — form_post SSO callbacks, payment returns, iframes, credentialed cross-site fetch — and the matching fixes.
Diagnose from symptoms (state mismatch, intermittent by timing), fix narrowly rather than blanket-None, and add cross-domain integration tests plus a dedicated metric.
Plan for third-party cookie deprecation: redesign flows onto redirects, same-site API hosts and server-to-server callbacks so the platform does not depend on cross-site cookies at all.
## What changed Historically a cookie with no `SameSite` attribute was sent on every request to its host, cross-site or not. That default made CSRF the web's default condition. Browsers changed it: an absent `SameSite` is now treated as `Lax`, and `SameSite=None` — the explicit opt-in to cross-site delivery — is only honoured when the cookie is also `Secure`, so third-party cookies must be HTTPS. Both changes are silent from the server's point of view. Nothing errors; the cookie just is not in the `Cookie` header. Symptoms are always some flavour of "the user appears logged out" or "state mismatch" in exactly one flow. ## The breakages, and why each happens **SSO callbacks using `response_mode=form_post` (SAML assertions, some OIDC configurations).** You set a state/nonce cookie, redirect the user to the IdP, and the IdP returns by making the browser **POST** the assertion to your callback URL. That POST is cross-site (initiated on the IdP's site, targeting yours) and its method is not safe, so a Lax-defaulted cookie is withheld. Your callback cannot find the state it stored and rejects the login. Fix: mark the transient state cookie `SameSite=None; Secure`, or switch the response mode to a redirect-based GET (`response_mode=query` / `fragment`), which Lax permits as a top-level safe-method navigation. **Payment provider returns.** Same shape — the PSP posts the result to your return URL and the session cookie is missing, so the confirmation page shows as anonymous. Same two fixes, plus a server-to-server webhook as the authoritative record so the UX path is not load-bearing for correctness. **Embedded iframes and widgets.** Your app rendered inside a partner's page is a cross-site subresource context; Lax withholds the cookie on *every* request in that frame, not just POSTs. Requires `None; Secure` — and increasingly `Partitioned` too, because browsers that block third-party cookies ignore `None` entirely. **Cross-site `fetch`/XHR with credentials.** A front end on `app-one.com` calling `api.app-two.com` with `credentials: 'include'` sends nothing under Lax. Either move the API under the same registrable domain (making it same-site), or use `None; Secure` plus proper CORS with `Access-Control-Allow-Credentials` and an explicit origin allowlist. **A subtle one — the transitional "Lax + POST" grace window.** Some browser versions temporarily allowed a *freshly set* cookie (roughly the first two minutes) on cross-site POSTs. Flows that appeared to work in testing failed in production for slower users and then failed everywhere when the window was removed. Never diagnose from a fast local run. ## Handling it 1. **Set `SameSite` explicitly on every cookie.** Relying on the default means your behaviour is defined by the user's browser version. Make the framework's cookie helper require the value. 2. **Classify cookies.** Most are `Lax`. A small set genuinely needs `None; Secure` — enumerate them and justify each, because each one is a cookie any site can cause to be sent, so its endpoints need CSRF tokens or origin checks independently. 3. **Prefer redesign over `None` where you can.** Redirect-GET response modes, same-registrable-domain API hosts, and server-to-server callbacks all remove the need for cross-site cookies. This also survives third-party cookie deprecation, which `None` does not. 4. **Guarantee HTTPS end to end.** `None` without `Secure` is dropped, so any lingering HTTP leg in a redirect chain kills the cookie. 5. **Test cross-site for real.** A test that drives everything on one origin never exercises this. Use a second registrable domain in integration tests (or a hosts-file alias) so cross-site POST callbacks and iframe embeds are actually covered, and assert on the presence of the cookie in the request, not just on the final page rendering. 6. **Instrument the failure.** Log "state cookie missing at callback" as a distinct metric rather than a generic auth failure; that is the signal that tells you a SameSite issue rather than an IdP problem.
- An SSO login fails with 'state mismatch' only in production, and only for some users. How does SameSite explain that?If the IdP returns via a cross-site POST, a Lax-defaulted state cookie is withheld and the callback cannot match state. It looked fine in browsers or versions that still had the transitional Lax+POST grace window for very recently set cookies, so fast local logins passed while slower real ones failed. Marking the state cookie SameSite=None; Secure, or switching to a redirect-based response mode, fixes it.
- Why is marking every cookie SameSite=None; Secure a bad blanket fix?It restores the pre-change behaviour where cookies ride on any cross-site request, re-enabling the CSRF shapes Lax blocks. It also does not survive third-party cookie blocking, so embedded cases will break again. Only cookies with a demonstrated cross-site requirement should be None, and their endpoints need independent CSRF defences.
saying these in an interview costs you the question
- Setting SameSite=None everywhere as a quick fix
- Forgetting that None without Secure means the cookie is not stored at all
- Assuming the transitional Lax+POST grace period still exists
- Testing cross-site flows on a single origin and concluding they work
- Believing SameSite=None guarantees the cookie works in third-party iframes going forward