- Home
- Cookie Attributes
- SameSite Cookie Attribute: CSRF Protection
Cookie Attribute
SameSite Cookie Attribute: CSRF Protection
Learn how the SameSite cookie attribute prevents CSRF attacks, the differences between Strict, Lax, and None, and when to use each.
TL;DR: SameSite controls cross-site cookie behavior. Use
Strictfor sensitive cookies,Laxfor general use,Noneonly when cross-site is required.
What is SameSite?
SameSite determines whether cookies are sent with cross-site requests. It’s your primary defense against CSRF attacks.
Set-Cookie: session=abc123; SameSite=Lax; Secure; HttpOnly
```text
## SameSite Values
| Value | Cross-Site Links | Cross-Site POST | Embedded (iframe) |
| ------ | ---------------- | --------------- | ----------------- |
| Strict | ❌ No | ❌ No | ❌ No |
| Lax | ✅ Yes | ❌ No | ❌ No |
| None | ✅ Yes | ✅ Yes | ✅ Yes |
### Strict
Never sends cookies cross-site. Maximum security, but can break UX:
```http
Set-Cookie: session=abc123; SameSite=Strict
User clicks link from email → arrives logged out (cookie not sent).
Lax (Default)
Sends cookies on top-level navigation (clicking links), blocks on POST and embeds:
Set-Cookie: session=abc123; SameSite=Lax
```text
User clicks link from email → arrives logged in ✓
Malicious form POST → cookie blocked ✓
### None
Sends cookies everywhere. Requires `Secure`:
```http
Set-Cookie: widget=xyz; SameSite=None; Secure
CSRF Protection
Without SameSite, a malicious site can trigger authenticated requests:
<!-- Attacker's site -->
<form action="https://bank.com/transfer" method="POST">
<input name="to" value="attacker" />
<input name="amount" value="10000" />
</form>
<script>
document.forms[0].submit()
</script>
```text
With `SameSite=Lax` or `Strict`, the session cookie isn't sent, blocking the attack.
## When to Use Each
| Use Case | Recommended |
| ---------------- | ---------------------------- |
| Session cookies | `Lax` or `Strict` |
| CSRF tokens | `Strict` |
| Remember me | `Lax` |
| Analytics | `Lax` |
| Embedded widgets | `None; Secure` |
| OAuth/SSO | `None; Secure` (during flow) |
## Implementation
### Express.js
```javascript
app.use(
session({
cookie: {
sameSite: 'lax',
secure: true,
httpOnly: true
}
})
)
// Or manually
res.cookie('session', token, {
sameSite: 'strict',
secure: true,
httpOnly: true
})
Different Cookies, Different Settings
// Session: Strict for maximum security
res.cookie('session', sessionId, {
sameSite: 'strict',
secure: true,
httpOnly: true
})
// CSRF token: Strict
res.cookie('csrf', csrfToken, {
sameSite: 'strict',
secure: true
})
// Preferences: Lax is fine
res.cookie('theme', 'dark', {
sameSite: 'lax',
secure: true
})
```text
## Common Mistakes
```javascript
// ❌ Wrong: None without Secure
res.cookie('session', token, { sameSite: 'none' })
// Browsers will reject this
// ✅ Correct: None requires Secure
res.cookie('session', token, { sameSite: 'none', secure: true })
// ❌ Wrong: Relying only on SameSite for CSRF
// SameSite=Lax allows GET requests
// ✅ Better: Combine with CSRF tokens for state-changing operations
app.post('/transfer', csrfProtection, (req, res) => {
// ...
})
Browser Support
All modern browsers support SameSite and default to Lax. Older browsers ignore the attribute entirely, so always combine with other protections.
Related
- HttpOnly cookie - Prevent XSS access
- Secure cookie - HTTPS only
- Set-Cookie header - Setting cookies
- Cookie security guide - Complete guide
SameSite and OAuth/SSO Flows
OAuth and SSO flows that use popup windows or cross-site redirects require careful SameSite configuration. During an OAuth authorization code flow, the identity provider redirects back to your application with an authorization code. If your session cookie has SameSite=Strict, the browser will not send it with this redirect because the redirect originates from the identity provider’s domain, making it a cross-site navigation.
The practical consequence is that users who click “Login with Google” arrive at your callback URL without their existing session cookie, causing your application to treat them as new visitors even if they were already logged in. The fix is to use SameSite=Lax for session cookies, which allows cookies to be sent with top-level navigations (including OAuth redirects) while still blocking cross-site POST requests.
For the temporary state cookie used during the OAuth flow (storing the state parameter to prevent CSRF), SameSite=None; Secure is required if the flow involves cross-site redirects. This cookie only needs to exist for the duration of the authorization flow, so a short Max-Age (5 minutes) limits the exposure window. After the flow completes and the state is validated, delete this cookie immediately.
Frequently Asked Questions
What is the SameSite cookie attribute?
SameSite controls whether cookies are sent with cross-site requests. It helps prevent CSRF attacks by restricting when cookies are included.
What is the difference between SameSite Strict and Lax?
Strict never sends cookies cross-site. Lax sends cookies on top-level navigations (clicking links) but not on cross-site POST or embedded requests.
What is the default SameSite value?
Modern browsers default to Lax if SameSite is not specified. This provides basic CSRF protection automatically.
When should I use SameSite=None?
Only when you need cross-site cookies (embedded widgets, OAuth flows). Requires Secure attribute. Avoid if possible.