Set Secure Cookie Attributes (HttpOnly, Secure, SameSite)
Share
Session cookies without HttpOnly, Secure, and SameSite are vulnerable to XSS theft and CSRF. The Next.js cookies() API does not enforce secure defaults. [CWE-614, CWE-1004 · A05:2021]
Why This Matters
prevents session hijacking via XSS and cross-site request forgery
Set Secure Cookie Attributes (HttpOnly, Secure, SameSite)
Impact: HIGH (prevents session hijacking via XSS and cross-site request forgery)
The Next.js cookies() API makes it easy to set cookies but does not enforce secure defaults. A session cookie missing HttpOnly can be stolen by XSS (document.cookie). A cookie missing Secure can be intercepted over HTTP. A cookie missing SameSite defaults to Lax in modern browsers, but explicit is better than implicit for security-critical cookies.
Incorrect (missing security attributes):
// ❌ No HttpOnly — any XSS can steal this cookieimport { cookies } from 'next/headers'export async function login(credentials: unknown) { const session = await createSession(credentials) cookies().set('session-token', session.token) // Default: no HttpOnly, no Secure, SameSite=Lax (browser default)}
// ❌ Explicitly insecurecookies().set('session-token', token, { httpOnly: false, // Accessible via document.cookie — XSS can steal it secure: false, // Sent over HTTP — vulnerable to MITM sameSite: 'none', // Sent on ALL cross-site requests — CSRF risk})
// ❌ Setting cookies via headers without attributesreturn new NextResponse(null, { headers: { 'Set-Cookie': `token=${value}`, // No attributes at all! },})
Correct (secure cookie configuration):
import { cookies } from 'next/headers'// ✅ Always set all three security attributes on session cookiesexport async function login(credentials: unknown) { const session = await createSession(credentials) cookies().set('session-token', session.token, { httpOnly: true, // Not accessible via JS secure: process.env.NODE_ENV === 'production', // HTTPS only in prod sameSite: 'lax', // Prevents most CSRF path: '/', // Available site-wide maxAge: 60 * 60 * 24 * 7, // 7 days })}
// ✅ For auth tokens that never need client-side accesscookies().set('auth-token', token, { httpOnly: true, secure: true, sameSite: 'strict', // Strictest: never sent cross-site path: '/', maxAge: 60 * 60 * 24, // 24 hours})// ✅ For CSRF tokens that DO need client-side accesscookies().set('csrf-token', csrfToken, { httpOnly: false, // Client JS needs to read this to send in headers secure: true, sameSite: 'strict', path: '/',})