Skip to main content

SameSite Cookies: Step-by-Step Setup

The SameSite attribute on cookies is a second line of defense against CSRF attacks. When you set SameSite=Strict (or Lax), the browser refuses to send the cookie with cross-origin requests, even if the user is already logged in. This eliminates entire classes of CSRF attacks without requiring CSRF tokens in every form. Most modern sites use both SameSite cookies and CSRF tokens: if one fails, the other remains. By 2026, SameSite is supported in all major browsers and is considered a baseline defense.

This article teaches you how SameSite works, the three modes, and how to configure it in your React application.

What SameSite Does

When a form on evil.com tries to submit a POST request to mybank.com, the browser checks the SameSite attribute of the session cookie. If the cookie is SameSite=Strict, the browser will not include the cookie with the request. The server receives a POST but with no session data, so it rejects the request. The attacker cannot exploit the user's authentication.

User is logged into: https://mybank.com (cookie: session=abc123; SameSite=Strict)
Attacker's form on: https://evil.com submits to https://mybank.com/transfer
Browser sends form to mybank.com but DOES NOT include the session cookie
Server receives request with no session, treats as unauthenticated, rejects request
CSRF attack is blocked at the browser level

SameSite is a cookie attribute, meaning it is set by the server (not the client) when issuing the cookie. The browser enforces the policy.

SameSite Modes: Strict, Lax, and None

ModeBehaviorUse Case
StrictCookie sent only for same-origin requests. Never sent with cross-origin requests.Maximum security; use for session cookies on banking/email. Small UX tradeoff: clicking a link from email to your site will not include the cookie on first load.
LaxCookie sent for same-origin requests and top-level navigations (link clicks). Not sent for subresource requests (img, script, fetch).Balance between security and UX; use for most sites. Form submissions from cross-origin if top-level navigation (i.e., user clicks a link).
NoneCookie sent with all requests, including cross-origin. Must include Secure attribute (HTTPS only).Required for third-party cookies (ads, analytics). Requires explicit opt-in and HTTPS.

Setting SameSite in Your Backend

The server sets SameSite when creating the session cookie. Here is how in different frameworks:

Node.js/Express:

const express = require('express');
const session = require('express-session');
const app = express();

app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true, // Prevent JavaScript access (important!)
secure: true, // HTTPS only
sameSite: 'Strict', // Enforce SameSite=Strict
maxAge: 3600000, // 1 hour
},
}));

app.get('/', (req, res) => {
res.send('Session cookie set with SameSite=Strict');
});

app.listen(3001);

Python/Flask:

from flask import Flask, session
from datetime import datetime, timedelta

app = Flask(__name__)
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)

@app.route('/')
def index():
session['user_id'] = 1
return 'Session cookie set with SameSite=Strict'

if __name__ == '__main__':
app.run(ssl_context='adhoc') # HTTPS

Go/Gin:

package main

import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)

func main() {
router := gin.Default()

store := cookie.NewStore([]byte("secret-key"))
store.Options = sessions.Options{
Path: "/",
MaxAge: 3600,
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
}
router.Use(sessions.SessionsMiddleware("session", store))

router.GET("/", func(c *gin.Context) {
session := sessions.Default(c)
session.Set("user_id", 1)
session.Save()
c.String(200, "Session cookie set with SameSite=Strict")
})

router.Run()
}

In all three examples, the cookie is created with SameSite=Strict, Secure=true (HTTPS only), and HttpOnly=true (JavaScript cannot access it via document.cookie).

Testing SameSite in Your React App

To verify SameSite is working, you can:

  1. Open your app in the browser and check the cookie in DevTools.
  2. Create a test page on a different domain that tries to submit a form to your app.
  3. Observe that the form submission fails (no session cookie included).

React component for testing:

import React, { useEffect, useState } from 'react';

export default function CookieInfo() {
const [cookieInfo, setCookieInfo] = useState(null);

useEffect(() => {
// Log cookie information to console
fetch('/api/cookie-info', { method: 'GET', credentials: 'include' })
.then(res => res.json())
.then(data => setCookieInfo(data))
.catch(err => console.error('Failed to fetch cookie info:', err));
}, []);

return (
<div>
<h2>Session Cookie Status</h2>
{cookieInfo ? (
<pre>{JSON.stringify(cookieInfo, null, 2)}</pre>
) : (
<p>Loading cookie information...</p>
)}
<p>
Check the Network tab in DevTools: click the request to your server
and look for the Set-Cookie response header. It should show
`SameSite=Strict; Secure; HttpOnly`.
</p>
</div>
);
}

Server endpoint to return cookie info:

app.get('/api/cookie-info', (req, res) => {
// This is informational; the actual SameSite enforcement
// happens at the browser level and is not exposed to JavaScript
res.json({
message: 'Check the Set-Cookie header in Network tab (DevTools).',
samesite: 'Strict',
secure: true,
httpOnly: true,
});
});

In the browser's Developer Tools (Network tab), click on the request to your server and look for the Set-Cookie response header. You should see something like:

Set-Cookie: session=abc123xyz789; Path=/; Secure; HttpOnly; SameSite=Strict

Choosing Between Strict and Lax

Use Strict if:

  • Your app handles sensitive operations (banking, email, healthcare, account changes).
  • Users primarily navigate within the app (not via external links).
  • A small UX tradeoff is acceptable.

Use Lax if:

  • Your app is a social network, news site, or e-commerce store where users often click links from email or social media.
  • You want to balance security and convenience.
  • You are already using CSRF tokens (so losing the cookie for some requests is not a problem).

Use None only if:

  • You need third-party cookies (rare for security-focused sites; more common for ads or embedded widgets).
  • You have explicitly allowed other sites to embed your content.
  • You must include Secure attribute (HTTPS only).

For most production React apps, SameSite=Lax or SameSite=Strict plus CSRF tokens is the standard.

Key Takeaways

  • SameSite is a cookie attribute set by the server that restricts when the browser sends the cookie with cross-origin requests.
  • Strict blocks the cookie for all cross-origin requests. Lax allows it for top-level navigation. None requires Secure (HTTPS) and allows all requests.
  • Always pair SameSite with Secure (HTTPS only) and HttpOnly (JavaScript cannot read it).
  • SameSite is a browser-enforced policy; JavaScript in your React app cannot override it.
  • Use SameSite together with CSRF tokens for layered defense.

Frequently Asked Questions

If I set SameSite=Strict, do I still need CSRF tokens?

Yes. SameSite is enforced by the browser, but cookies can be stolen (e.g., via XSS or network sniffing on HTTP). CSRF tokens add a second layer: even if the cookie is sent (or None mode is used for third-party cookies), the token must still match. Use both.

With Strict, the browser does not send the cookie when navigating from another site, so the first page load is unauthenticated. The user must log in again or click "Log in" from your login page. With Lax, the cookie is sent for top-level navigations (link clicks), so the user stays logged in. This is a UX tradeoff.

Can I set SameSite in JavaScript from my React component?

No. SameSite is a server-side header. Your React code cannot modify it. The server must set it when creating the session cookie.

What about third-party cookies and SameSite=None?

If your site embeds content from another site (e.g., ads, analytics), that third-party site needs SameSite=None; Secure to set cookies. However, modern browsers increasingly restrict third-party cookies. Check your analytics/ad provider's documentation.

Further Reading