The problem
Browsers attach your cookies to every request that targets your domain, whichever site started that request. If a user visits a malicious page while logged into your application, any request that page makes to your server, such as a hidden form submission or an image tag, includes the user’s session cookie automatically. To your server, that request looks just like a genuine one, because it includes valid credentials.
The pattern
The SameSite cookie attribute tells the browser when it’s allowed to send a cookie on a cross-site request. The browser enforces it, so it works without any token validation in your application code. The attribute takes three values, listed here from most to least strict.
Problem
A cross-site request forgery (CSRF) attack abuses this behavior. An attacker embeds a form that POSTs to /account/transfer or /settings/email, the victim’s browser includes the session cookie, and the action runs under the victim’s identity without them knowing. The user never clicked anything that looked dangerous, but a page they had no reason to trust was able to use their logged-in session. You want a cookie that the browser only sends with requests from your own site, so your users’ credentials never go along with a request that another site started.
Solution
Strict is the most secure. The cookie is never sent on any cross-site request, including top-level navigations. If a user clicks a link to your app from an email or another website, the browser doesn’t send the cookie, so the user arrives logged out even though they have a valid session. That makes CSRF practically impossible, but it’s awkward for people clicking through from search results or a newsletter, who land on a logged-out page and have to navigate again before your app shows them as logged in.
Lax suits most session cookies, and Chrome and other Chromium-based browsers apply it when no SameSite value is specified. The cookie is sent on same-site requests and on top-level GET navigations from other sites, such as someone clicking a link to your app, but it isn’t sent with cross-site subrequests like form POSTs, AJAX calls, images, and iframes. That stops the kinds of request a CSRF attack uses, and links into your app from other sites still work. Someone clicking through from an email arrives logged in, but a hidden form on an attacker’s page can’t send an authenticated request.
With None, the browser sends the cookie on all requests, including cross-site ones. You need it for legitimate cross-site uses, such as third-party embeds or federated SSO. Browsers reject SameSite=None unless the cookie is also marked Secure, so it’s only sent over HTTPS. Choosing None brings back the cross-site risk that the attribute is there to prevent, so you should only use it for cookies that really do need to be sent cross-site.
Whichever value you choose, it works best alongside Secure, which stops the cookie being sent over unencrypted connections, and HttpOnly, which stops JavaScript from reading it, so an XSS attack can’t steal it. SameSite is a strong layer of protection, but it doesn’t cover everything. Some older browsers don’t support it properly, it doesn’t protect endpoints that change state on a GET request, and an XSS attack on your own site gets around it completely, so you still need CSRF tokens on endpoints that change state.
Example
These examples show how to set the attribute directly on the Set-Cookie header, how to configure it from a Node server, and how to pick a value for common use cases.
Setting the attribute via Set-Cookie
The attribute is part of the Set-Cookie response header, and you can combine each value with Secure and HttpOnly.
# Strict: never sent cross-site (best for sensitive session cookies)
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnly; Path=/
# Lax: sent on top-level GET navigations (the default for most sessions)
Set-Cookie: sessionId=abc123; SameSite=Lax; Secure; HttpOnly; Path=/
# None: sent on all cross-site requests (REQUIRES Secure)
Set-Cookie: widgetPrefs=xyz789; SameSite=None; Secure; Path=/
Setting it from a server
In Express, the sameSite option sets the attribute, and the browser does the enforcing.
// Strict session cookie for a first-party app
res.cookie('sessionId', 'abc123', {
sameSite: 'strict', // never sent on cross-site requests
secure: true, // only sent over HTTPS
httpOnly: true, // not readable by JavaScript (mitigates XSS theft)
maxAge: 1000 * 60 * 60 * 24, // 1 day
});
// Lax suits sessions where inbound links must keep users logged in
res.cookie('sessionId', 'abc123', {
sameSite: 'lax',
secure: true,
httpOnly: true,
});
// None is required for legitimate cross-site cookies (embeds, SSO)
// The browser rejects SameSite=None unless Secure is also set
res.cookie('widgetPrefs', 'xyz789', {
sameSite: 'none',
secure: true, // mandatory with sameSite: 'none'
});
Choosing a value
The value you pick depends on what the cookie is used for.
| Use case | Value | Reason |
|---|---|---|
| First-party session, high-value app | Strict | The strongest CSRF protection, and fine if users don’t need to arrive logged in from links |
| First-party session, general web app | Lax | Blocks cross-site subrequests, and users still arrive logged in from links |
| SSO cookie on a cross-site POST back | None | The identity provider POSTs back cross-site, as with SAML or form_post, and Secure is required |
| Third-party embedded widget | None | The cookie has to be sent on cross-site requests, and Secure is required |
| Double-submit CSRF token cookie | Lax | Your own pages read it and send it back in a header or form field |
Benefits
- It stops the browser from sending cookies with requests that other sites started, which cuts off the mechanism CSRF attacks depend on.
- The browser enforces it on every request, so there’s no per-route logic or application-level check for you to maintain.
Laxis the default in Chromium-based browsers, so cookies get some protection there even if you forget to set the attribute.- You set it with a single attribute on the
Set-Cookieheader, without adding middleware or passing tokens around. - Combined with
SecureandHttpOnly, the same cookie is also protected from being sent over unencrypted connections and from being stolen through XSS. Strictmakes CSRF practically impossible for cookies that never need to be sent cross-site.- For low-risk endpoints, you might not need to rely on CSRF tokens as much, which keeps that part of your defense simpler.
Tradeoffs
- With
Strict, users who click through from an email or search results arrive logged out and have to navigate again. Nonebrings back the cross-site risk that the attribute is there to prevent, and it requiresSecure, so it only works over HTTPS.SameSitedoesn’t give you complete CSRF protection. You still need tokens on endpoints that change state, especially any that do it on a GET request.- Older browsers handle
SameSiteinconsistently, and some legacy clients, such as Safari 12, treatNoneasStrictor ignore the attribute entirely. app.example.comandblog.example.comcount as the same site, soSameSitewon’t stop requests from one of your subdomains to another.- If you forget
Secureon aSameSite=Nonecookie, the browser silently rejects it, and the cross-site flows that depend on it break in ways that are hard to debug. - Different cookies often need different values, so one global setting rarely works for all the cookies in your application.
- The browser treats a “site” as the registrable domain plus the scheme, which is broader than a full origin, so this might surprise you if you expect the attribute to work per origin.
Summary
The SameSite attribute controls whether the browser attaches a cookie to cross-site requests, and the right value blocks the mechanism that CSRF depends on. Lax is a sensible default for session cookies, Strict works when users don’t need to arrive logged in from links, and None should only be used, always with Secure, for cookies that have to be sent cross-site. It works as a strong layer alongside Secure and HttpOnly, but it doesn’t replace CSRF tokens.
See also
- CSRF Protection — Security
- Secure Token Storage — Security
- Content Security Policy (CSP) — Security
- SameSite Cookie Attribute - MDN
- CSRF Protection - OWASP
- Cookie Security - web.dev