# SameSite Cookies Explained: Strict, Lax and None

The `SameSite` attribute on a cookie decides whether the browser sends it on requests that come **from another site**. It's the main reason cross-site request forgery (CSRF) is much harder than it used to be — and the reason some logins, payment redirects and embedded widgets mysteriously "forget" who you are.

```http
Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
```

## "Site" is not "origin"

SameSite works on **sites**, which are broader than origins. A site is the scheme plus the registrable domain: `https://app.example.com` and `https://api.example.com` are different origins but the **same site** (`https://example.com`). A request between them is same-site, so SameSite doesn't restrict it.

`https://example.com` and `https://example.org` are different sites. So are `http://example.com` and `https://example.com` — browsers now treat the scheme as part of the site.

## The three values

| Value | Sent on cross-site requests? |
|---|---|
| `Strict` | Never. Not even when the user clicks a link to your site from elsewhere. |
| `Lax` | Only on top-level navigations with a safe method — following a link, typing the URL. Not on cross-site POSTs, iframes, images or fetches. |
| `None` | Always. Requires `Secure`. |

What that means for common requests arriving from another site:

| Request from another site | Strict | Lax | None |
|---|---|---|---|
| User clicks a link to your page | ✗ | ✓ | ✓ |
| Form POST to your site | ✗ | ✗ | ✓ |
| Your page in an `<iframe>` | ✗ | ✗ | ✓ |
| `<img>`, `<script>` or `fetch()` to your site | ✗ | ✗ | ✓ |

## The default is Lax — in some browsers

Chrome and Edge treat a cookie without a `SameSite` attribute as `Lax`. Firefox and Safari don't apply that default, so the same cookie behaves differently across browsers. Always set `SameSite` explicitly.

Chrome also has a small exception for cookies with no attribute: for the first two minutes after they're set, they're still sent on cross-site top-level POSTs, so older single sign-on flows keep working. Cookies that say `SameSite=Lax` explicitly don't get that grace period.

## Which one to use

- **Session cookies for a normal website: `Lax`.** Users who follow a link to you arrive logged in, and cross-site POSTs — the classic CSRF vector — arrive without the cookie.
- **Sensitive actions (banking, admin): `Strict`**, often on a second cookie. The trade-off: users clicking a link from email or chat land on your site looking logged out until the next navigation.
- **Cookies that must work cross-site — embedded widgets, third-party integrations: `None; Secure`.** Expect browsers to restrict these further: Safari blocks third-party cookies by default and Firefox partitions them per site.

## Why logins break: the cross-site POST

Some sign-in providers and payment pages send the user back to your site with a **form POST** (OAuth's `response_mode=form_post`, Sign in with Apple, many 3-D Secure flows). That's a cross-site POST, so a `Lax` or `Strict` cookie holding the login state isn't sent, and your callback can't find the session.

Fixes, in order of preference:

1. Use a redirect (GET) callback if the provider supports it.
2. Mark only the short-lived state cookie used by the callback as `SameSite=None; Secure`, and keep the session cookie `Lax`.

## SameSite and CSRF

`Lax` stops most CSRF, because a forged form on another site can't make the browser send your cookie with a POST. It isn't complete protection:

- **Same-site isn't same-origin.** A compromised or user-controlled subdomain (`evil.example.com`) is same-site and can still send cookie-bearing requests.
- **GET requests that change state** are still exposed to top-level navigations. Don't change data on GET.
- Older browsers ignore the attribute.

Keep CSRF tokens or check the [Origin header](/headers/origin) on state-changing requests, and treat SameSite as a strong second layer.

## Debugging

In Chrome DevTools, open **Application → Cookies** to see each cookie's SameSite value, and the **Network** tab's Cookies view for a request: blocked cookies are shown with the reason, such as "this cookie was blocked because it had the SameSite=Lax attribute and the request was made from a different site".

For every cookie attribute — `HttpOnly`, `Secure`, `Max-Age`, `Domain`, `Path` — see the [Set-Cookie reference](/headers/set-cookie); for sending cookies on cross-origin fetches, see [Access-Control-Allow-Credentials](/headers/access-control-allow-credentials).
