Cross-Site Request Forgery

A forged request is one your user's browser makes on someone else's behalf, carrying your session cookie because cookies are attached by origin, not by intent. CSRF Protection After csurf builds the token defense for Express 24,430 ; Server Actions need less of it. Three things stack up: SameSite=lax keeps the cookie off cross-site POSTs and is now the browser default; Server Actions are POST-only, so no image tag or link can trigger one; and Next.js 10,514 compares Origin against Host (or X-Forwarded-Host) on every action request, aborting on a mismatch.

A Server Action POST claiming to come from another siteShell
curl -s -o /dev/null -w "%{http_code}\n" -X POST localhost:3319/dashboard \
  -H "Cookie: bookshelf_session=$LEM" -H "Origin: https://evil.example.com" \
  -H "Next-Action: 004391bdaa2249bd49db608c1a57b28ec282312189" \
  -H "Content-Type: text/plain;charset=UTF-8" --data '[]'
Output
500
# and in the server log:
`x-forwarded-host` header with value `localhost:3319` does not match `origin`
header with value `evil.example.com` ... Aborting the action.
 ⨯ Error: Invalid Server Actions request.

The action function never ran. Behind a reverse proxy that rewrites Host, the same check rejects your own traffic; the fix is serverActions.allowedOrigins in next.config.ts — not turning the check off.

Two gaps remain. Route handlers get nothing: their origin is never compared, so check request.headers.get("origin") yourself, or require something a cross-site form cannot send, such as Content-Type: application/json. And GET must not mutate: SameSite=lax still sends the cookie on top-level navigations, so a handler that deletes on GET is reachable from an <img> tag anywhere in the world.