HTTP

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.

3 min read intermediate Try in Playground

TL;DR: SameSite controls cross-site cookie behavior. Use Strict for sensitive cookies, Lax for general use, None only 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.

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.

Keep Learning