SameSite=Lax: Understanding the Default Cookie Policy

SameSite=Lax is the default SameSite attribute value in modern browsers and represents a balanced approach to cookie security that protects against CSRF attacks while maintaining usability for normal navigation.

What is SameSite=Lax?

SameSite=Lax is a cookie attribute that controls when cookies are sent with cross-site requests. It provides a middle ground between security and usability:

Understanding when cookies with SameSite=Lax are sent requires analyzing three key factors:

  1. Same-site vs cross-site: Is the request going to the same domain where the cookie was set?
  2. Top-level navigation: Does the request change the URL bar (top-level) or load embedded content?
  3. HTTP method: Is the request using GET or POST?

Same-Site Requests (Always Sent)

Same-site requests occur when a request is made to the same domain where the cookie was set. Cookies with SameSite=Lax are always sent with same-site requests, regardless of HTTP method or request type.

Let's say a user is on site-a.com and navigates to another page on site-a.com (e.g., clicks a link to site-a.com/products). This is a same-site request. Both Lax and Strict cookies are sent to site-a.com because the request is going to the same domain where the cookies were set.

Examples of same-site requests:

Result: Cookie is always sent with same-site requests.

Cross-Site Requests

Cross-site requests occur when a request is made to a different domain than where the cookie was set. The behavior depends on whether it's a top-level navigation and the HTTP method used.

Scenario 1: Cross-Site GET Navigation (Lax Cookies Sent)

Let's say a user is on site-a.com and clicks on a link to go to site-b.com. This is a cross-site request. This is a top-level navigation and is a GET request, so Lax cookies are sent to site-b.com. However, Strict cookies are not sent because it is, after all, a cross-site request.

Key characteristics:

Other examples of cross-site GET navigation:

Scenario 2: Cross-Site Embedded Resources (Lax Cookies NOT Sent)

The user is on site-a.com and there is an iframe in which site-b.com is loaded. This is a cross-site request, but it's not a top-level navigation (the user is still on site-a.com, i.e. the URL bar doesn't change when the iframe is loaded). Therefore neither Lax nor Strict cookies are sent to site-b.com.

Key characteristics:

Other examples of cross-site embedded resources:

Scenario 3: Cross-Site POST Requests (Lax Cookies NOT Sent)

The user is on site-a.com which POSTs a form to site-b.com. This is a cross-site request, but the method (POST) is unsafe. It doesn't meet the criteria for Lax cookies going cross-site, so neither Lax nor Strict cookies are sent to site-b.com.

Key characteristics:

Why this protects against CSRF:

Other examples of cross-site POST requests:

Scenario Same-Site? Top-Level? Method SameSite=Lax SameSite=Strict
User on site-a.com navigates to site-a.com/products ✅ Yes N/A Any ✅ Sent ✅ Sent
User on site-a.com clicks link to site-b.com ❌ No ✅ Yes GET ✅ Sent ❌ Not sent
User on site-a.com has iframe loading site-b.com ❌ No ❌ No GET ❌ Not sent ❌ Not sent
User on site-a.com POSTs form to site-b.com ❌ No ✅ Yes POST ❌ Not sent ❌ Not sent

Key Takeaways:

Understanding Top-Level Navigation

Key Concept

Top-level navigation is the critical distinction for SameSite=Lax. It refers to navigation that changes the browser's main window/tab URL to a different domain.

Visual Indicators

Top-Level Navigation:

Non-Top-Level (Embedded):

Decision Flow

flowchart TD A[Request Made] --> B{Same Domain?} B -->|Yes| C[✅ Cookie Sent] B -->|No| D{Top-Level Navigation?} D -->|Yes| E{GET Method?} D -->|No| F[❌ Cookie NOT Sent] E -->|Yes| C E -->|No| F

Security Implications

CSRF Protection

SameSite=Lax provides protection against Cross-Site Request Forgery (CSRF) attacks:

How CSRF Protection Works:

Example Attack Prevented:

<!-- Malicious site: attacker.com -->
<form method="POST" action="https://bank.com/transfer">
  <input name="to" value="attacker-account">
  <input name="amount" value="1000">
</form>
<script>document.forms[0].submit();</script>

With SameSite=Lax:

Without SameSite=Lax (or with SameSite=None):

What's NOT Protected

SameSite=Lax does NOT protect against:

  1. Cross-Site GET Requests: Cookies are sent with GET navigations

    • Can be exploited if GET endpoints perform side effects
    • Best practice: Use POST for state-changing operations
  2. Same-Site Attacks: Cookies are always sent with same-site requests

    • Requires additional protection (CSRF tokens, etc.)
  3. XSS Attacks: If site has XSS vulnerability, cookies can be stolen

    • Use HttpOnly attribute to prevent JavaScript access

Browser Default Behavior

Modern Browsers (2020+)

Most modern browsers default to SameSite=Lax if no SameSite attribute is specified:

Legacy Behavior

Older browsers or browsers with legacy settings may:

Best Practice: Always explicitly set SameSite=Lax for clarity and compatibility.

Real-World Examples

Example 1: E-commerce Site

Scenario: User shops on shop.example.com, then clicks link from email to continue shopping.

Setup:

Set-Cookie: session=abc123; Domain=shop.example.com; SameSite=Lax; Secure

Flow:

  1. User receives email from newsletter.example.com
  2. Email contains link: <a href="https://shop.example.com/checkout">Complete Purchase</a>
  3. User clicks link
  4. Result: Cookie IS sent (cross-site GET navigation)

Why it works: User expects to stay logged in when clicking email links.

Scenario: User logs in to app.example.com, then clicks link from documentation site.

Setup:

Set-Cookie: auth_token=xyz789; Domain=app.example.com; SameSite=Lax; Secure; HttpOnly

Flow:

  1. User visits docs.example.com
  2. Documentation links to: <a href="https://app.example.com/dashboard">Open App</a>
  3. User clicks link
  4. Result: Cookie IS sent (cross-site GET navigation)

Why it works: User expects seamless navigation between related sites.

Example 3: CSRF Protection for Payment

Scenario: Payment form on checkout page.

Setup:

Set-Cookie: session=abc123; Domain=shop.example.com; SameSite=Lax; Secure

Flow:

  1. User on shop.example.com checkout page
  2. Form: <form method="POST" action="https://shop.example.com/payment">
  3. User submits payment
  4. Result: Cookie IS sent (same-site POST)

Malicious Attack Attempt:

  1. Attacker creates page: <form method="POST" action="https://shop.example.com/payment">
  2. User visits attacker's page
  3. Attacker attempts to submit form
  4. Result: Cookie NOT sent (cross-site POST blocked)

Why it works: CSRF protection prevents unauthorized payment submissions.

Example 4: Analytics Tracking Pixel

Scenario: Third-party analytics pixel embedded on website.

Setup:

Set-Cookie: tracking_id=123; Domain=analytics.example.com; SameSite=Lax

Flow:

  1. User visits shop.example.com
  2. Page includes: <img src="https://analytics.example.com/pixel?id=123">
  3. Pixel loads
  4. Result: Cookie NOT sent (cross-site embedded resource)

Why it fails: SameSite=Lax blocks cookies from embedded resources.

Solution: Use SameSite=None; Secure for third-party tracking cookies.

Comparison with Other SameSite Values

SameSite=Lax vs SameSite=Strict

Scenario SameSite=Lax SameSite=Strict
Same-site requests ✅ Sent ✅ Sent
Cross-site GET navigation ✅ Sent ❌ Not sent
Cross-site POST ❌ Not sent ❌ Not sent
Cross-site embedded resources ❌ Not sent ❌ Not sent
User experience More flexible More restrictive
Security CSRF protection Maximum security

When to use Strict:

When to use Lax:

SameSite=Lax vs SameSite=None

Scenario SameSite=Lax SameSite=None
Same-site requests ✅ Sent ✅ Sent
Cross-site GET navigation ✅ Sent ✅ Sent
Cross-site POST ❌ Not sent ✅ Sent
Cross-site embedded resources ❌ Not sent ✅ Sent
Security CSRF protection No CSRF protection
Requires Secure No Yes

When to use None:

Common Pitfalls and Best Practices

Pitfall 1: Assuming POST Requests Work Cross-Site

Problem:

// This won't work cross-site
fetch('https://api.example.com/data', {
  method: 'POST',
  credentials: 'include' // Still won't send SameSite=Lax cookie
});

Solution: Use SameSite=None; Secure if you need cross-site POST cookies, or redesign to use same-site requests.

Pitfall 2: Expecting Embedded Resources to Send Cookies

Problem:

<!-- Cookie won't be sent -->
<iframe src="https://widget.example.com"></iframe>

Solution: Use SameSite=None; Secure for widget cookies, or use postMessage API for communication.

Pitfall 3: Not Setting SameSite Explicitly

Problem: Relying on browser defaults may cause inconsistent behavior.

Solution: Always explicitly set SameSite=Lax:

Set-Cookie: session=abc; SameSite=Lax; Secure

Best Practice 1: Use HttpOnly with SameSite=Lax

Recommended:

Set-Cookie: session=abc; SameSite=Lax; Secure; HttpOnly

Why: Prevents JavaScript access, reducing XSS attack surface.

Best Practice 2: Always Use Secure with SameSite=Lax

Recommended:

Set-Cookie: session=abc; SameSite=Lax; Secure

Why: Ensures cookies only sent over HTTPS, protecting against man-in-the-middle attacks.

Best Practice 3: Test Cross-Site Scenarios

Action: Test your application with:

Testing Scenarios

Test 1: Same-Site Navigation

  1. Visit https://site-a.cookie-playground.pun7o.click
  2. Set cookie: test=value1; Domain=site-a.cookie-playground.pun7o.click; SameSite=Lax
  3. Navigate to https://site-a.cookie-playground.pun7o.click/index.html
  4. Expected: Cookie IS sent

Test 2: Cross-Site GET Navigation

  1. Visit https://site-b.cookie-playground.pun7o.click
  2. Set cookie: test=value2; Domain=site-a.cookie-playground.pun7o.click; SameSite=Lax
  3. Click link from subdomain B to subdomain A
  4. Expected: Cookie IS sent (cross-site GET navigation)

Test 3: Cross-Site POST

  1. Visit https://site-b.cookie-playground.pun7o.click
  2. Set cookie: test=value3; Domain=site-a.cookie-playground.pun7o.click; SameSite=Lax
  3. Submit POST form from subdomain B to subdomain A
  4. Expected: Cookie NOT sent (cross-site POST blocked)

Test 4: Cross-Site Pixel Request

  1. Visit https://site-b.cookie-playground.pun7o.click
  2. Set cookie: test=value4; Domain=site-a.cookie-playground.pun7o.click; SameSite=Lax
  3. Load pixel: <img src="https://site-a.cookie-playground.pun7o.click/pixel">
  4. Expected: Cookie NOT sent (embedded resource)

Test 5: Same-Site POST

  1. Visit https://site-a.cookie-playground.pun7o.click
  2. Set cookie: test=value5; Domain=site-a.cookie-playground.pun7o.click; SameSite=Lax
  3. Submit POST form on same domain
  4. Expected: Cookie IS sent (same-site request)

Summary

Key Points

  1. Default Behavior: SameSite=Lax is the default in modern browsers
  2. Same-Site: Cookies always sent with same-site requests
  3. Cross-Site GET: Cookies sent with top-level GET navigation
  4. Cross-Site POST: Cookies NOT sent (CSRF protection)
  5. Embedded Resources: Cookies NOT sent (privacy protection)

Use Cases

Security Benefits