SameSite Cookie Attribute
SameSite controls when a cookie is sent with cross-site requests. Strict — never sent cross-site. Lax — sent on top-level navigation only (browser default since Chrome 80). None — always sent, requires Secure.
What SameSite does
Without SameSite, cookies are sent with every request to a domain — including requests initiated by other sites. This enables Cross-Site Request Forgery (CSRF) attacks. SameSite restricts when browsers include cookies in cross-site requests.
The three values
| Value | When cookie is sent | Use case |
|---|---|---|
Strict | Same-site requests only. Never cross-site — even clicking a link from another site. | Session cookies for banking, admin panels. Highest protection. |
Lax | Same-site requests + top-level cross-site navigation (GET only). Not sent on cross-site POST, iframes, AJAX. | Most web apps. Browser default since Chrome 80. |
None | All requests, including cross-site. Requires Secure flag. | Third-party cookies, OAuth flows, embedded widgets, cross-site auth. |
SameSite=Lax — what actually gets blocked
Lax blocks the cookie on cross-site:
- POST requests (form submissions from another site)
- AJAX / fetch requests with credentials
- Iframes loading your site inside another site
- Images, scripts loading your endpoint
Lax allows the cookie on cross-site:
- Clicking a link from another site to your site (top-level GET navigation)
- Typing your URL directly
SameSite=None — requires Secure
Browsers reject SameSite=None without Secure. The cookie must also be sent over HTTPS. Chrome enforces this since version 80.
Set-Cookie: session=abc123; SameSite=None; Secure; HttpOnly
How to set SameSite in each stack
Nginx
add_header Set-Cookie "session=VALUE; SameSite=Lax; Secure; HttpOnly; Path=/" always;
Express
res.cookie('session', value, {
sameSite: 'lax', // 'strict' | 'lax' | 'none'
secure: true,
httpOnly: true
});
FastAPI / Starlette
response.set_cookie(
key="session",
value=value,
samesite="lax", # "strict" | "lax" | "none"
secure=True,
httponly=True
)
Django
# settings.py SESSION_COOKIE_SAMESITE = 'Lax' # 'Strict' | 'Lax' | 'None' SESSION_COOKIE_SECURE = True SESSION_COOKIE_HTTPONLY = True CSRF_COOKIE_SAMESITE = 'Lax' CSRF_COOKIE_SECURE = True
Laravel
# config/session.php 'same_site' => 'lax', // 'strict' | 'lax' | 'none' 'secure' => true,
Browser default — SameSite=Lax
Since Chrome 80 (February 2020), cookies without an explicit SameSite attribute are treated as SameSite=Lax. Firefox and Safari followed. If your app depended on cookies being sent cross-site without SameSite=None, it broke in 2020.
SameSite vs CSRF tokens
SameSite=Strict or Lax provides CSRF protection for most use cases. However it is not a complete replacement for CSRF tokens because: it does not protect against attacks from the same site, some older browsers do not support it, and Lax allows cross-site GET requests which can be exploited in some cases. Use both for defense in depth.
Debugging SameSite issues
Chrome DevTools → Application → Cookies shows the SameSite value for each cookie. The Network tab shows whether cookies were included in specific requests. Missing cookies on cross-site requests is almost always a SameSite or Secure flag issue.