Chrome's SameSite Cookie Change Is Fully Live — Here's What Silently Breaks
If a login flow, payment callback or embedded widget on your site started misbehaving sometime in the second half of this year — but only for some users, and never when you tested it yourself — there's a decent chance you've been bitten by Chrome's new cookie defaults. The change has been a long time coming: Chrome 80 began treating cookies without a SameSite attribute as SameSite=Lax back in February, Google paused the rollout in April to avoid breaking essential services during the early pandemic, then quietly re-enabled it from Chrome 84 over the summer. As of now, it's simply how Chrome behaves — for essentially everyone.
The cruelty of this particular breakage is that it's invisible from the inside. Your site works. Your tests pass. But any flow that depends on a cookie being sent in a cross-site request — and an awful lot of auth and payment plumbing does — silently loses that cookie in Chrome unless the cookie explicitly opts in. Here's what changed, what breaks, and how to fix and test it.
What actually changed
SameSite is a cookie attribute that controls whether a browser attaches the cookie to requests initiated from other sites. It has three values:
Strict— the cookie is never sent on cross-site requests, even when the user clicks a link to your site.Lax— the cookie is sent on top-level navigations (clicking a link), but not on cross-site POSTs, iframes, or subresource requests.None— the old free-for-all: sent on all cross-site requests. Under the new rules this now requires theSecureflag —SameSite=Noneover plain HTTP is rejected outright.
The change: cookies that specify nothing — which is most cookies ever set — used to behave like None. Chrome now treats them as Lax. Firefox and Edge have signaled the same direction, so this is the web's new default, not a Chrome quirk.
There's one softening detail worth knowing: Chrome currently applies a "Lax + POST" exception, letting a defaulted cookie ride along on a top-level cross-site POST if the cookie is less than two minutes old. It exists precisely to keep flows like payment redirects limping along — and it's documented as temporary. If your checkout only works because of a two-minute grace window, you have a bug with a fuse on it.
What breaks in practice
The failures cluster around any flow where site B needs a cookie set by site A:
- Cross-site POST callbacks — the classic being payment: your customer pays on the provider's page, gets POSTed back to
yoursite.com/payment/complete, and arrives without their session cookie. Your app treats them as logged out; the order looks unpaid; support tickets ensue. - SSO and OAuth redirects — identity flows bounce users between domains, and several steps depend on cookies surviving the round trip. Symptoms: login loops, "state mismatch" errors, users dumped back at the sign-in page after successfully authenticating.
- Embedded iframes — anything of yours embedded on another site (a booking widget, a dashboard, an embedded checkout) makes cross-site requests by definition. With defaulted cookies, the iframe's session never sticks: users appear logged out inside the frame, forever.
- CSRF-protected forms posted from another origin — the session cookie doesn't arrive, so the token check fails with a misleading "invalid token" error that sends you debugging entirely the wrong thing.
Note what doesn't break: normal same-site browsing, and plain links into your site (Lax allows top-level navigations). That's exactly why this slips through testing — everything looks fine until a cross-site flow runs.
The fix: say what you mean
Cookies that legitimately need to travel cross-site must now declare it explicitly — both attributes, together:
Set-Cookie: widget_session=abc123; Path=/; Secure; HttpOnly; SameSite=None
The rules that matter:
SameSite=NonerequiresSecure. No HTTPS, no cross-site cookie, full stop. (If any auth or payment flow of yours still touches plain HTTP in late 2020, that's the first fix.)- Most cookies should NOT get
None. The new default is a genuine security win —Laxneutralizes most classic CSRF attacks for free. Audit which cookies truly need cross-site behavior (usually one or two), opt those in, and set everything else toLaxorStrictexplicitly rather than relying on defaults. - Set it at whatever layer owns the cookie. Most frameworks now expose this in session config — for anything hand-rolled in PHP, for example:
session_set_cookie_params([
'secure' => true,
'httponly' => true,
'samesite' => 'None', // only for genuinely cross-site cookies
]);
The Safari wrinkle
One trap for the thorough: older Safari (macOS 10.14 / iOS 12) has a known bug that treats SameSite=None as SameSite=Strict — the exact opposite of what you asked for. Sites with meaningful traffic from those versions often resort to user-agent detection, omitting the attribute for the buggy clients and sending None; Secure to everyone else. It's ugly, it's temporary, and whether you need it depends entirely on your analytics — check before adding the complexity.
How to test it
- Read the console. Chrome's DevTools console warns specifically when a cross-site cookie was blocked for lacking
SameSite=None; Secure— run your cross-site flows and read what it tells you. - Inspect the cookie headers. DevTools → Application → Cookies shows each cookie's SameSite value; the Network tab shows which cookies actually accompanied a given request. "The cookie exists but wasn't sent" is the signature of this whole class of bug.
- Simulate the strict future.
chrome://flags/#same-site-by-default-cookiesand#cookies-without-same-site-must-be-securelet you force the new behavior on hard (no two-minute grace) — the closest thing to testing next year's Chrome today. - Walk the real flows end to end. A real payment in test mode, a real SSO login, your widget embedded on a page hosted on a different domain. Cross-site bugs only appear cross-site.
Broken-for-some is the hardest downtime to catch
This change is a good security default for the web — and a textbook example of the failure mode that hurts most: the site is "up," but a specific flow is broken, for users on a specific browser, arriving from a specific place. Nobody inside the building sees it.
That's an argument for monitoring your critical flows, not just your homepage — checks on the endpoints behind login and checkout, keyword assertions that catch an error page hiding behind a 200, and alerts the moment behavior changes. CompleteStatus does exactly that (we covered the fundamentals in what uptime monitoring is) — create a free account and put a monitor on the flows this change likes to break quietly.