CSRF Protection
Rules
- Set
SameSite=Lax on all cookies — this blocks most CSRF attacks by default
- Use
SameSite=Strict for highly sensitive actions (account deletion, password change)
- Implement double-submit cookie pattern for custom APIs: CSRF token in cookie + request header
- Include CSRF tokens in all HTML forms that perform state-changing actions
- Verify
Origin and Referer headers on state-changing requests as defense-in-depth
- Next.js Server Actions and SvelteKit form actions handle CSRF automatically — use them
- For SPAs: send CSRF token in a custom header (
X-CSRF-Token) — browsers block cross-origin custom headers by default
import crypto from "crypto";
function generateCsrfToken(res) {
const token = crypto.randomBytes(32).toString("hex");
res.cookie("csrf_token", token, { httpOnly: false, sameSite: "strict", secure: true });
return token;
}
function verifyCsrf(req) {
const cookieToken = req.cookies.csrf_token;
const headerToken = req.headers["x-csrf-token"];
if (!cookieToken || cookieToken !== headerToken) {
throw new Error("CSRF validation failed");
}
}
Avoid
- Relying only on cookie-based auth without CSRF protection — any site can submit forms to your endpoints
- CSRF tokens that never rotate — one leaked token compromises all future requests
- GET requests that change state — CSRF protection only works on POST/PUT/DELETE because GET requests bypass SameSite=Lax
- Disabling CSRF protection because "it's just an API" — if cookies are involved, CSRF applies