Cross-site request forgery (CWE-352) allows a malicious page to trigger authenticated state-changing requests in a victim's browser using their existing session cookie. Without CSRF protection, any site can silently transfer funds, change email addresses, or delete accounts on behalf of a logged-in user. OWASP A01 (Broken Access Control) ranks CSRF among the most exploited web vulnerabilities. NIST 800-53 SC-23 requires session authenticity protection. The attack requires no special privileges — a single malicious link is sufficient. Cookies with SameSite=Lax or Strict are the minimal defense; APIs accepting cookie-authenticated requests need explicit origin validation on top.
High because a successful CSRF attack lets any website perform authenticated actions as the victim without their knowledge, with no interaction beyond visiting a page.
For Next.js apps, validate the Origin header on every state-changing route and ensure cookies are SameSite=Lax at minimum:
export function validateOrigin(req: Request): boolean {
const origin = req.headers.get('origin')
const host = req.headers.get('host')
if (!origin || !host) return false
return new URL(origin).host === host
}
For richer CSRF protection, use the csrf-csrf double-submit cookie pattern in src/middleware.ts. Do not rely on SameSite=None — it provides zero CSRF protection.
ID: security-hardening.auth-session.csrf-protection
Severity: high
What to look for: Count every state-changing endpoint (POST, PUT, PATCH, DELETE routes) in the application. For each, check how state-changing requests (POST, PUT, PATCH, DELETE) are protected against cross-site request forgery. Look for CSRF token middleware (csurf, csrf-csrf, edge-csrf), SameSite cookie attributes, custom request header validation (e.g., checking Origin or X-Requested-With headers), or framework-provided CSRF protection. Note that SameSite=Strict or SameSite=Lax on session cookies provides meaningful CSRF protection for most use cases.
Pass criteria: State-changing API endpoints are protected by at least one of: CSRF tokens validated server-side, SameSite=Strict or SameSite=Lax cookie attribute on auth cookies, custom request header requirement (X-Requested-With or similar), or origin validation — 100% of state-changing endpoints must have CSRF protection. Report: "X state-changing endpoints found, all Y protected with CSRF tokens or SameSite cookies."
Fail criteria: POST/PUT/PATCH/DELETE routes accept session-cookie-authenticated requests with no CSRF protection, and cookies are not set with SameSite=Strict or SameSite=Lax.
Skip (N/A) when: The application uses token-based auth (JWT in Authorization header, not cookies) where CSRF is not applicable. Or the project is an API with no browser-facing endpoints.
Detail on fail: Identify the exposed routes. Example: "POST /api/transfer and POST /api/settings accept cookie-authenticated requests without CSRF tokens; cookies lack SameSite attribute" or "Auth cookies set with SameSite=None — CSRF attacks possible from any origin"
Remediation: The simplest approach for most Next.js or similar apps is to ensure session cookies use SameSite=Lax (the browser default for modern browsers, but should be explicit) combined with Origin header validation:
// Validate origin for state-changing requests
export function validateOrigin(req: Request): boolean {
const origin = req.headers.get('origin')
const host = req.headers.get('host')
if (!origin || !host) return false
return new URL(origin).host === host
}
// Or use the csrf-csrf library for double-submit cookie pattern
import { doubleCsrf } from 'csrf-csrf'
const { generateToken, validateRequest } = doubleCsrf({
getSecret: () => process.env.CSRF_SECRET!,
cookieName: '__Host-csrf',
cookieOptions: { sameSite: 'strict', secure: true },
})