TL;DR
Session cookies without proper flags can be stolen via XSS (missing HttpOnly), network attacks (missing Secure), or CSRF (missing SameSite). Always set all three flags on authentication cookies: HttpOnly, Secure, and SameSite=Lax or Strict.
Cookie Security Flags
| Flag | What It Does | Prevents |
|---|---|---|
| HttpOnly | Cookie not accessible via JavaScript | XSS cookie theft |
| Secure | Cookie only sent over HTTPS | Network interception |
| SameSite=Strict | Cookie only sent on same-site requests | CSRF attacks |
| SameSite=Lax | Sent on same-site + top-level navigation | Most CSRF attacks |
The Problem
Insecure cookie setting
// Missing all security flags!
res.cookie('session', token);
// What this actually means:
// - JavaScript can read it (XSS can steal it)
// - Sent over HTTP (can be intercepted)
// - Sent on cross-site requests (CSRF possible)
The Fix
Secure cookie settings
// Express example
res.cookie('session', token, {
httpOnly: true, // Can't be accessed by JavaScript
secure: true, // Only sent over HTTPS
sameSite: 'lax', // Not sent on cross-site requests
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
path: '/'
});
// Next.js API route
import { cookies } from 'next/headers';
cookies().set('session', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7
});
Should I use SameSite Strict or Lax?
Use Lax for most cases. Strict breaks legitimate flows like clicking links from emails. Lax allows cookies on top-level navigation while still blocking cross-site POST requests.
What about cookie prefixes like __Host-?
Cookie prefixes add extra security. __Host- requires Secure, no Domain, and Path=/. __Secure- requires the Secure flag. These prevent subdomain attacks.
Do I need Secure in development?
Localhost is usually exempted. Use secure: process.env.NODE_ENV === 'production' to apply the flag only in production.