Skip to main content

Chrome's SameSite Cookie Change Is Fully Live — What Silently Breaks

Chrome now treats cookies as SameSite=Lax by default, breaking cross-site POSTs, SSO and payment callbacks. What changed, what breaks, and how to fix it.

The CompleteStatus Team 6 min read

Chrome's SameSite Cookie Change Is Fully Live — What Silently Breaks

Want this checked continuously?

CompleteStatus grades your headers, SSL and email security 24/7 — free, commercial use allowed.

Monitor your site free

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 resumed it with Chrome 84 in July and completed it in August. It's now simply how Chrome behaves, for essentially everyone.

What makes this breakage nasty 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 a 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 requires the Secure flag; SameSite=None without it 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. Chromium-based Edge follows Chrome, and Firefox has been testing the same default, so this is becoming the web's default rather than a Chrome quirk.

One softening detail: 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 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 case is 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.
  • 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 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 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, with both attributes:

Set-Cookie: widget_session=abc123; Path=/; Secure; HttpOnly; SameSite=None

The rules that matter:

  • SameSite=None requires Secure. 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 improvement: Lax neutralizes 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 to Lax or Strict explicitly 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 (7.3+), for example:
session_set_cookie_params([
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'None',   // only for genuinely cross-site cookies
]);

The Safari wrinkle

One more trap: 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 and temporary, and whether you need it depends 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.
  • Force the behavior on. chrome://flags/#same-site-by-default-cookies and #cookies-without-same-site-must-be-secure let you enable the new behavior explicitly in a test profile, so you're not relying on where your browser sits in the rollout.
  • 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.

Written with AI assistance and reviewed by the CompleteStatus team.