Login CSRF (CAPEC-62) lets an attacker log a victim into an attacker-controlled account using a forged cross-site request. This enables account takeover indirectly: the victim, now logged into the attacker's account, may enter sensitive data that the attacker can later retrieve. CWE-352 (CSRF) on a login endpoint is classified under OWASP A01 (Broken Access Control); PCI-DSS v4.0 Req-6.2.4 explicitly lists cross-site request forgery among the software attacks that engineering practices must prevent. SameSite=Lax provides meaningful browser-enforced protection for most cases, but explicit CSRF token validation is required for custom endpoints that process sensitive actions on navigation.
Critical because login CSRF can silently associate a victim's activity with an attacker's account, enabling credential harvesting and session theft without any direct compromise of the victim's credentials.
If using NextAuth, CSRF protection is built into the credential callback — do not bypass the standard signin form flow. For custom endpoints, add explicit CSRF token validation:
import { csrf } from '@edge-csrf/nextjs'
const csrfProtect = csrf({ cookie: { secure: true } })
export async function POST(request: Request) {
const csrfError = await csrfProtect(request)
if (csrfError) return Response.json({ error: 'Invalid CSRF token' }, { status: 403 })
// login logic
}
Verify session cookies have SameSite=Lax set (see the session-tokens-secure check) as a defense-in-depth layer. Do not rely on SameSite alone when you can add a CSRF token.
ID: saas-authentication.auth-flow.login-csrf
Severity: critical
What to look for: Examine the login endpoint for CSRF protection. In NextAuth: CSRF protection is built-in via the csrfToken mechanism — verify the login form uses the /api/auth/csrf endpoint to retrieve the token. In custom endpoints: check for CSRF token validation middleware, SameSite=Strict or SameSite=Lax cookie enforcement, or Origin/Referer header validation. For cookie-based sessions, SameSite=Lax provides meaningful protection for top-level navigations. Count all instances found and enumerate each.
Pass criteria: The login endpoint is protected against CSRF via one or more of: a CSRF token validated server-side, SameSite=Strict session cookies, or an auth library that includes built-in CSRF protection (NextAuth, Clerk). The protection covers the POST that establishes the session. At least 1 implementation must be confirmed.
Fail criteria: Custom login endpoint accepts POST requests with no CSRF token validation and uses cookies with SameSite=None or unset SameSite. No Origin/Referer validation present.
Skip (N/A) when: Application uses only JSON API authentication with bearer tokens (no cookies) — CSRF requires cookie-based sessions. Signal: auth is entirely stateless JWT with no session cookies.
Cross-reference: The session-tokens-secure check verifies the cookie security flags that complement CSRF token validation.
Detail on fail: "Login endpoint at /api/auth/login accepts POST with session cookies but has no CSRF token, no Origin validation, and cookies use SameSite=None".
Remediation: CSRF on a login endpoint allows an attacker to log victims into an attacker-controlled account (login CSRF), enabling account takeover indirectly. If using NextAuth, CSRF protection is already built in — verify you're using the standard signin flow:
// NextAuth handles CSRF automatically via csrfToken
// Your custom login form should POST to /api/auth/callback/credentials
// with the csrfToken from /api/auth/csrf
// For custom endpoints, validate CSRF token:
import { csrf } from '@edge-csrf/nextjs'
const csrfProtect = csrf({ cookie: { secure: true } })
export async function POST(request: Request) {
const csrfError = await csrfProtect(request)
if (csrfError) return Response.json({ error: 'Invalid CSRF token' }, { status: 403 })
// ... login logic
}
Setting SameSite=Lax on session cookies provides meaningful protection in modern browsers as a defense-in-depth measure.