The Synchronizer Token Pattern (STP)
The Synchronizer Token Pattern is the gold-standard application-level defense against CSRF. When a user establishes a session, the server generates a cryptographically strong pseudo-random token and binds it to the user's session.
When rendering forms or issuing API mutations, the server injects this token into the client (via a hidden form field or custom HTTP header). When processing state-changing requests (POST, PUT, DELETE), the server compares the submitted token against the session token. Because the Same-Origin Policy prevents attacker.com from reading tokens on bank.com, the attacker cannot forge a valid request.
// Node.js Express CSRF Token Verification Middleware
import crypto from 'crypto';
import type { Request, Response, NextFunction } from 'express';
export function verifyCsrfToken(req: Request, res: Response, next: NextFunction) {
// Safe HTTP methods do not change state
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
const clientToken = req.headers['x-csrf-token'] || req.body?._csrf;
const sessionToken = req.session?.csrfToken;
if (!clientToken || !sessionToken || !crypto.timingSafeEqual(Buffer.from(clientToken), Buffer.from(sessionToken))) {
return res.status(403).json({ error: 'CSRF token verification failed' });
}
next();
}Why CORS Does NOT Protect Against CSRF
A frequent and dangerous misconception among developers is believing that Cross-Origin Resource Sharing (CORS) prevents CSRF attacks.
CORS is a browser mechanism that restricts whether a web page can read the response of a cross-origin request. However, CSRF is an attack on state execution, not data retrieval. When a malicious form submits a POST request cross-site, the browser dispatches the request and the server executes the database mutation - even if CORS blocks the attacker from reading the response! CORS does not replace anti-CSRF defenses.
The Chromium 2-Minute 'Lax-allowing-unsafe' Exception
A critical security nuance in modern Chromium-based browsers (Chrome, Edge, Brave, Opera) is the 2-minute Lax exception:
When a cookie is created without an explicit SameSite attribute, Chromium defaults to SameSite=Lax. However, to prevent breaking legacy single sign-on (SSO) redirect flows, Chromium implements a temporary exemption: for the first 120 seconds after creation, top-level cross-site POST requests are permitted to include the cookie!
This means an attacker who induces a victim to click a link within 2 minutes of logging in can successfully execute CSRF if your application relies solely on default SameSite settings. Explicit cryptographic Anti-CSRF tokens or custom request headers (`X-Requested-With`) remain strictly mandatory. You can audit CSRF defenses across your endpoints using the WeSecureX Vulnerability Explorer.
Apply it with WeSecureX
Add a hostname you own or are allowed to test, run a scan, and open the findings for that target. A confirmed result includes the request WeSecureX sent and the response the site returned, so you can reproduce it, fix it in your own application, and rescan the same host to confirm the issue is gone. WeSecureX is automated testing, not a manual penetration test, and a clean report is not a promise that every weakness is gone.
WeSecureX modules for this topic
Checks that belong with this topic
- Set `SameSite=Lax` or `SameSite=Strict` on all authentication and session cookies.
- Ensure all GET, HEAD, and OPTIONS requests are strictly read-only and idempotent.
- Implement Synchronizer Token Pattern or HMAC-signed Double-Submit Cookies on all state-changing endpoints.
- Use constant-time comparisons (`crypto.timingSafeEqual`) to validate tokens.
- Require custom request headers (e.g., `X-Requested-With` or `X-CSRF-Token`) for JSON API mutations.
- Re-authenticate users before high-risk actions (password changes, email updates, wire transfers).
Common Mistakes to Avoid
❌Performing state-changing actions via GET requests (e.g., `/user/delete?id=5`)
Why it happens: Developers create simple links for convenience instead of forms or POST requests.
Why it matters: GET requests can be triggered by simple `<img>` tags (`<img src='bank.com/transfer?amount=100'>`), completely bypassing `SameSite=Lax` protection.
Correct approach: Strictly adhere to HTTP specifications: GET, HEAD, and OPTIONS must be safe and idempotent. All mutations must use POST, PUT, PATCH, or DELETE.
❌Assuming CORS configuration protects against CSRF
Why it happens: Developers see 'CORS error' in the console and assume the request was blocked.
Why it matters: CORS blocks the client from reading the response; the server still receives, parses, and executes the state-changing request.
Correct approach: Implement SameSite cookies and anti-CSRF tokens regardless of CORS configuration.
❌Using static or predictable CSRF tokens
Why it happens: Developers derive tokens from user IDs or timestamps.
Why it matters: Predictable tokens allow attackers to calculate the required token and forge requests successfully.
Correct approach: Generate tokens using a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG, e.g., `crypto.randomBytes(32)`).
❌Comparing CSRF tokens with standard equality operators (`===`)
Why it happens: Developers write `if (clientToken === sessionToken)`.
Why it matters: Standard string comparisons terminate on the first non-matching byte, exposing the server to timing attacks.
Correct approach: Use constant-time comparison functions like `crypto.timingSafeEqual()`.
Troubleshooting Guide
Problem: Legitimate users receive 403 CSRF verification failed after opening multiple browser tabs
Possible Causes:
- The server generates a new CSRF token on every page load and overwrites the session token, invalidating tokens in earlier tabs.
How to verify: Open two tabs, submit a form in tab 1, and check for 403 Forbidden.
How to fix: Bind a single CSRF token per user session rather than per request, or implement token rotation with grace periods.
Problem: Mobile app or external API clients fail with CSRF errors
Possible Causes:
- CSRF middleware is applied globally to all API endpoints, including non-browser clients.
How to verify: Send a request via curl or Postman and inspect for CSRF validation failures.
How to fix: Only enforce CSRF checks on requests authenticated via session cookies; API clients using `Authorization: Bearer <JWT>` are not vulnerable to CSRF.
Actionable Checklist
WeSecureX Platform · Scanner Engine
Automated Vulnerability Scanner
Launch targeted or full-domain security audits to detect OWASP Top 10 vulnerabilities automatically.
Includes: OWASP Top 10 automated test suites · Real-time severity scoring (CVSS v3.1) · Instant remediation code snippets
Frequently Asked Questions
Standard cross-origin HTML forms can only send `application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`. However, modern browsers can send cross-origin `application/json` via `fetch()` if CORS is misconfigured. Requiring custom headers (like `X-CSRF-Token`) triggers a browser CORS preflight check, effectively blocking CSRF.
If the JWT is stored in an `HttpOnly` cookie, the application IS vulnerable to CSRF and requires anti-CSRF tokens. If the JWT is stored in memory and sent via the `Authorization: Bearer <token>` header, it is immune to CSRF (though vulnerable to XSS if tokens are exposed).
Chromium introduced a temporary mitigation where cookies without a SameSite attribute default to `Lax`, but permit cross-site POST requests within 2 minutes of creation ('Lax+POST'). Explicitly specifying `SameSite=Lax` or `SameSite=Strict` prevents this behavior.
SameSite=Lax blocks cross-site POST form submissions and cross-site sub-requests (images, iframes). However, Chromium has a temporary 2-minute exception for newly created cookies, so cryptographic anti-CSRF tokens remain necessary.
The client reads a non-HttpOnly random token cookie and copies it into a custom HTTP request header (e.g. X-CSRF-Token). The server compares the cookie value with the header value, blocking cross-origin attackers who cannot read cookies.
Authoritative Sources & References
- OWASP Cross-Site Request Forgery Prevention Cheat SheetOWASP Foundation (official)View Source
- IETF RFC 6265bis: Cookies: HTTP State Management MechanismIETF (official)View Source
- MDN Web Docs: SameSite cookiesMozilla (technical)View Source
- IETF RFC 6265bis: SameSite Cookie Attribute SpecificationIETF (official)View Source
Related Guides
Continue exploring related technical architecture and defensive guides
sessions · cookies
Secure Session Management: Cookies, Tokens & Best Practices
Technical guide to session security: cryptographically secure cookie prefixes (`__Host-`), session fixation defense, idle timeouts, and Redis session architectures.
authentication · passwords
Secure Authentication Architecture: Passwords, MFA & Passkeys
Developer blueprint for authentication architecture: Argon2id password hashing parameters, FIDO2/WebAuthn passkey implementation, and credential stuffing defense.
API security · REST
REST API Security Best Practices for Modern Applications
Developer blueprint for securing REST APIs: BOLA/IDOR prevention patterns, asymmetric JWT validation, Redis token bucket rate limiting, and strict input DTOs.
