Skip to main content
Saved
Pattern
Difficulty Advanced

SameSite Cookie

Configure cookie SameSite attributes to control cross-site cookie behavior and prevent CSRF attacks.

Den Odell
By Den Odell Added

SameSite Cookie

Problem

Browsers attach your cookies to every request that targets your domain, and they don’t care who started that request. When a user visits a malicious page while logged into your application, any request that page makes to your server—a hidden form submission, an image tag, an iframe—travels with the user’s session cookie automatically. The forged request looks identical to a genuine one because, from your server’s point of view, it carries valid credentials.

This is exactly the mechanism a cross-site request forgery (CSRF) attack abuses. An attacker embeds a form that POSTs to /account/transfer or /settings/email, the victim’s browser quietly includes the session cookie, and the action executes under the victim’s identity without their knowledge or consent. The user never clicked anything that looked dangerous; their authenticated session was simply borrowed by a page they didn’t trust. What you actually want is a cookie that only rides along on requests originating from your own site, so credentials never accompany requests kicked off by someone else.

Solution

The SameSite cookie attribute tells the browser when it is allowed to send a cookie on a cross-site request. It is enforced by the browser itself, so it works automatically without any token validation in your application code. The attribute takes three values that map to increasingly strict policies.

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 withholds the cookie, so the user arrives logged out even though they have a valid session. That makes CSRF practically impossible, but it can hurt usability on inbound links—people clicking through from search results, newsletters, or shared URLs land on a logged-out page and have to navigate again to appear authenticated.

Lax is the sensible default for most session cookies, and it is what modern browsers apply when no SameSite value is specified. The cookie is sent on same-site requests and on top-level GET navigations from other sites (someone clicking a link to your app), but it is withheld from cross-site subrequests like form POSTs, AJAX calls, images, and iframes. This blocks the dangerous CSRF vectors while keeping inbound links working: click through from email and you arrive logged in, but a hidden form on an attacker’s page cannot fire an authenticated request.

None sends the cookie on all requests, cross-site included, and is required for legitimate cross-site use cases like third-party embeds, federated SSO, or widgets hosted on another origin. Browsers reject SameSite=None unless the cookie is also marked Secure, so it can only travel over HTTPS. Reaching for None reintroduces the cross-site exposure that the attribute exists to prevent, so use it only when a flow genuinely depends on cross-site cookies.

Whichever value you choose, pair it with Secure (sent only over HTTPS) and HttpOnly (inaccessible to JavaScript) for defense in depth: Secure stops cookies leaking over plaintext connections, HttpOnly blocks XSS-based theft, and SameSite blocks CSRF. Treat SameSite as a strong layer rather than a complete defense—it does not cover every scenario (older browsers, GET-based state changes, same-site XSS), so keep CSRF tokens for state-changing endpoints.

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.

The attribute is part of the response header. Each value combines with Secure and HttpOnly for a hardened cookie:

# 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 maps to the attribute and the browser handles enforcement:

// 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 is a safer default when 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

Match the value to how the cookie is actually used:

Use caseValueWhy
First-party session, high-value appStrictMaximum CSRF protection; acceptable if no inbound-link logins
First-party session, general web appLaxBlocks CSRF subrequests while keeping inbound links logged in
Federated SSO / OAuth callback cookieNoneFlow depends on the cookie crossing origins; must add Secure
Third-party embedded widgetNoneCookie must travel on cross-site requests; must add Secure
CSRF token cookie (double-submit)LaxNeeds to accompany top-level navigations; pair with a token

Benefits

  • Cuts off the core CSRF mechanism by stopping cookies from riding along on requests started by other sites.
  • Enforced by the browser, so protection applies automatically to every request without application-level checks.
  • Lax is the modern browser default, so most cookies get baseline protection even if a developer forgets to set the attribute.
  • Configured with a single attribute on the Set-Cookie header—no extra dependencies, middleware, or token plumbing required.
  • Layers cleanly with Secure and HttpOnly to defend against plaintext leakage and XSS theft at the same time.
  • Strict makes CSRF practically impossible for cookies that never need to travel cross-site.
  • Works uniformly across requests once set, with no per-route logic to maintain.
  • Reduces reliance on CSRF tokens for low-risk endpoints, simplifying parts of your defense.

Tradeoffs

  • Strict breaks inbound-link UX: users clicking through from email, search, or shared URLs arrive logged out and must navigate again.
  • None reintroduces the exact cross-site exposure the attribute exists to prevent, and forces Secure, so it only works over HTTPS.
  • SameSite is not complete CSRF protection on its own—keep tokens for state-changing endpoints, especially GET requests that mutate state.
  • Older browsers handle SameSite inconsistently, and some legacy clients treat unknown values as Strict or ignore the attribute entirely.
  • Subdomains need care: a cookie scoped to example.com is considered same-site with app.example.com, so SameSite won’t isolate requests between your own subdomains.
  • Forgetting Secure with SameSite=None causes the browser to silently drop the cookie, breaking cross-site flows in ways that are hard to debug.
  • Different cookies often need different values, so a single global setting rarely fits every use case across an application.
  • The definition of “site” is registrable-domain plus scheme, not full origin, which surprises teams expecting strict per-origin behavior.

Summary

The SameSite attribute controls whether the browser attaches a cookie to cross-site requests, neutralizing the mechanism CSRF depends on. Use Lax as a sensible default for session cookies, reach for Strict when no inbound-link logins are needed, and limit None (always with Secure) to genuine cross-site flows. Treat it as a strong layer alongside Secure, HttpOnly, and CSRF tokens rather than a standalone defense.

Newsletter

A Monthly Email
from Den Odell

Behind-the-scenes thinking on frontend patterns, site updates, and more

No spam. Unsubscribe anytime.