
- CORS controls whether scripts can read responses.
- Browsers send simple form posts directly to web servers.
- Use SameSite cookies, anti-CSRF tokens, and origin checks.
Why CORS policies fail to stop cross-site request forgery
Does CORS prevent CSRF attacks on web APIs? No. For simple requests, browsers send payloads before checking CORS rules. Only for preflighted requests does the browser send an OPTIONS check first. This guide breaks down browser limits, execution paths, and safe API designs.
Browsers guard user data with two key rules. The first rule is the Same-Origin Policy. It stops rogue scripts from reading data across domains. The second rule is Cross-Origin Resource Sharing. CORS lets servers relax that wall for trusted partners. Mixing up read checks and write blocks leads to serious flaws.
Cross-Site Request Forgery targets logged-in accounts. Victims are lured onto adversarial pages while authenticated. That bad page triggers a hidden request to a target app. The browser attaches the user's login cookies on its own. Unless the app checks for forgery, it runs the action right away.
Difference between CORS read rules and CSRF write actions
To see the difference between cors and csrf, inspect how requests travel. CORS protects privacy. It decides if code can read the data coming back from a server.
CSRF attacks server state. Read access does not matter. The attacker only wants to trigger a state-changing write action on your backend, such as updating an account email or wiring money.
Browsers do not block simple requests before dispatch. CORS provides zero outbound transmission barriers. When the payload reaches your server, the backend executes the mutation immediately. The client script on the attacker's page cannot inspect the response text, but your system state has already changed permanently.
How simple requests bypass preflight checks
The W3C Fetch rules split requests into two groups. The first group covers simple requests. The second group covers preflighted requests.
A call is simple if it uses GET, HEAD, or POST. Its content type must be urlencoded, multipart form data, or plain text. Simple calls cannot include headers outside the CORS-safelist like Accept or Accept-Language.
Browsers never send preflight checks for simple calls. They send the payload straight away. This keeps old web forms running smoothly. Because of this rule, an attacker can submit a hidden form from their site right into your app.
Evaluating CORS and CSRF defense mechanics
| Security dimension | Cross-Origin Resource Sharing (CORS) | Cross-Site Request Forgery (CSRF) Defenses |
|---|---|---|
| Primary objective | Governs cross-origin data reading permissions | Prevents unauthorized state-changing actions |
| Enforcement location | Client browser engine inspecting response headers | Server middleware validating request provenance |
| Impact on simple POST | Delivers request, hides response from calling script | Rejects unauthenticated or forged requests upfront |
| Role of preflight checks | Blocks complex requests missing OPTIONS consent | Indirectly blocks calls requiring unforgeable headers |
| Credential exposure | Access-Control-Allow-Credentials gates script access | Ambient session cookies exploited during forged calls |
| Required mitigation | Strict Access-Control-Allow-Origin configurations | SameSite cookies, anti-CSRF tokens, origin headers |
When CORS preflight provides incidental request protection
CORS does not stop CSRF by design. Preflights only help modern APIs by pure accident. When an API accepts only complex calls, the browser pauses and asks first.
Consider an endpoint that needs a JSON body or a custom header. The browser sends an OPTIONS preflight. OWASP guidelines require explicit token or SameSite cookie defenses rather than relying on content-type gating.
If your server rejects the preflight origin, the browser halts. The state-changing POST or PUT body is dropped. But content types are not enough. If your handler takes form data by mistake, attackers can slip past this guard.
Three essential layers of robust CSRF protection
Real web safety needs solid server rules instead of CORS guesses. Development teams must combine three proven controls across all state-changing routes.
Start by setting SameSite on every login cookie. Using SameSite=Lax stops cookies on cross-site sub-calls. Yet it keeps normal links working for users. For high-risk actions, SameSite=Strict blocks cookies on all outside visits.
Next, check Origin and Referer headers in your server code. Browsers set these headers on their own. Client code cannot alter them. If an outside domain sends a post, return a 403 error right away.
Last, use cryptographically random anti-CSRF tokens for all session actions. Give each user session a unique token. The app must pass this secret with every write. For stateless microservices, OWASP also recommends the double-submit cookie pattern as a practical alternative.
Production middleware configuration for Express
The snippet below is a simplified reference implementation. In production, use an audited CSRF package alongside constant-time token comparisons:
// Express security middleware for API endpoints
const crypto = require('crypto');
const express = require('express');
const app = express();
const ALLOWED_ORIGINS = new Set(['https://techwebmagazine.com']);
app.use((req, res, next) => {
const origin = req.headers.origin;
// Enforce CORS origin reflection for approved hosts only
if (origin && ALLOWED_ORIGINS.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,X-Requested-With,X-CSRF-Token');
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
// Handle preflight requests cleanly
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
// Enforce CSRF protection on mutating methods
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
// 1. Reject untrusted cross-origin requests
if (origin && !ALLOWED_ORIGINS.has(origin)) {
return res.status(403).json({ error: 'Untrusted origin' });
}
// 2. Validate anti-CSRF token using constant-time comparison
const csrfToken = req.headers['x-csrf-token'];
const sessionSecret = req.session?.csrfSecret;
if (!csrfToken || !sessionSecret) {
return res.status(403).json({ error: 'Missing CSRF credentials' });
}
const tokenBuf = Buffer.from(csrfToken);
const secretBuf = Buffer.from(sessionSecret);
if (tokenBuf.length !== secretBuf.length || !crypto.timingSafeEqual(tokenBuf, secretBuf)) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
}
next();
});
Origin rules fit into wider system defenses. In our guide to Basic Auth vs Bearer Token flows, we compare token models. Review script rules in our breakdown of CSP nonce vs hash policies. To boost network speed, read our analysis on TLS 1.3 vs TLS 1.2 handshake speed.
Frequently asked questions about CORS and CSRF
Does CORS prevent CSRF attacks?
No. CORS controls whether client scripts can read response bodies. It never stops browsers from sending requests.
Why do simple requests skip CORS preflight checks?
Browsers keep old web forms working. Standard form posts existed long before CORS.
Does a custom header stop CSRF on APIs?
Yes, if your API requires custom headers and validates both the Origin and header values before processing requests.
What is the best defense against CSRF attacks?
Use SameSite cookies on all session keys. Combine them with secret tokens and origin checks.