CORS and CSRF get lumped together way too often.
I’ve seen teams say “we enabled CORS, so we’re protected from CSRF now” and then act surprised when their app still has a cross-site request forgery bug. That’s the core mistake: these are not competing solutions to the same problem.
They deal with different threats, at different layers, using different browser behavior.
If you only remember one thing, make it this:
- CORS controls which websites can read responses from your server
- CSRF is about stopping unwanted state-changing requests made on behalf of a logged-in user
That’s the practical difference. Everything else is detail.
Quick definition
What is CORS?
CORS stands for Cross-Origin Resource Sharing.
It’s a browser security mechanism that decides whether JavaScript running on one origin can read a response from another origin.
Without CORS, the browser’s same-origin policy blocks that cross-origin read.
Example:
- Your frontend runs on
https://app.example.com - Your API runs on
https://api.example.com
If frontend JavaScript calls the API, the browser checks CORS headers like:
Access-Control-Allow-Origin: https://app.example.com
If the headers don’t allow it, the request may still be sent, but the browser blocks JavaScript from seeing the response.
That distinction matters a lot.
What is CSRF?
CSRF stands for Cross-Site Request Forgery.
It happens when a malicious site causes a victim’s browser to send a request to another site where the victim is already authenticated.
Classic example:
- User is logged into
bank.com - They visit
evil.com evil.comtricks the browser into submitting a request tobank.com/transfer- The browser includes the victim’s cookies automatically
- The bank sees a valid authenticated request unless it has CSRF defenses
CSRF is not about reading data. It’s about abusing ambient authentication, especially cookies.
The biggest difference
Here’s the cleanest way to separate them:
| Topic | CORS | CSRF |
|---|---|---|
| Main purpose | Control cross-origin response sharing | Prevent unwanted authenticated actions |
| Protects against | Unauthorized cross-origin reads by browser JS | Cross-site forged requests |
| Browser behavior involved | Same-origin policy + CORS headers | Automatic cookie sending |
| Applies to | Cross-origin frontend/API access | Any state-changing endpoint using cookie auth |
| Solved with | Access-Control-* headers |
CSRF tokens, SameSite cookies, origin checks |
If you’re building APIs, this is where people get tripped up:
- CORS is a browser access-control policy
- CSRF is an application-level request authenticity problem
They can interact, but one does not replace the other.
Why CORS does not stop CSRF
This is the part developers usually misunderstand.
Say your app has this endpoint:
POST /api/email/change
Cookie: session=abc123
If a user is logged in, their browser automatically sends the session cookie when making a request to your site.
Now an attacker hosts this:
<form action="https://app.example.com/api/email/change" method="POST">
<input type="hidden" name="email" value="[email protected]">
</form>
<script>
document.forms[0].submit();
</script>
That forged request can still be sent by the browser.
CORS does not stop that. Why? Because CORS mainly controls whether attacker-controlled JavaScript can read the response. It does not magically prevent every cross-site request from being made.
A simple form POST doesn’t need to read the response to cause damage.
That’s why relying on CORS for CSRF defense is a bad idea.
Why CSRF protection does not solve CORS
Now flip it around.
Imagine your frontend at https://app.example.com needs to fetch data from https://api.example.com.
Even if your API uses perfect CSRF tokens, the browser still won’t let frontend JavaScript read the response unless CORS is configured correctly.
Example fetch:
fetch("https://api.example.com/user", {
credentials: "include"
})
.then(r => r.json())
.then(console.log);
Your API might need:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Without that, the browser blocks access to the response.
CSRF protection doesn’t help here because this is not a forged-request problem. It’s a cross-origin browser access problem.
Real-world CORS example
GitHub’s API is a nice real example. api.github.com returns:
access-control-allow-origin: *
access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
That tells browsers:
- any origin can access the resource (
*) - JavaScript is allowed to read those listed non-simple response headers
That’s a CORS decision. It says nothing about CSRF.
GitHub exposing ETag, Link, and rate-limit headers is useful for API consumers, but it doesn’t mean “requests are protected from forgery.” Totally different concern.
If you want to inspect headers like this quickly, HeaderTest is handy for checking what a server is actually returning.
Pros and cons of CORS
CORS isn’t “good” or “bad.” It’s just necessary when browsers talk across origins.
Pros
-
Enables modern frontend architecture
Separate frontend and API origins are common now. CORS makes that workable. -
Fine-grained control
You can allow specific origins, methods, headers, and credentials. -
Browser-enforced
When configured correctly, the browser does the blocking for you. -
Can safely expose selected response headers
Stuff like pagination, rate limits, and ETags often needsAccess-Control-Expose-Headers.
Cons
-
Easy to misconfigure
Wildcards, credentialed requests, bad origin reflection — I see these constantly. -
Often misunderstood as a server-side auth feature
It’s not. Non-browser clients don’t care about CORS. -
Does not stop CSRF
This misconception causes real vulnerabilities. -
Preflight adds complexity
OPTIONShandling, custom headers, and debugging can get annoying fast.
Pros and cons of CSRF protection
CSRF defenses are mandatory if you use cookie-based auth for browser requests.
Pros
-
Stops forged actions from other sites
This is the direct fix for the actual problem. -
Works independently of CORS
Even if a request can be sent cross-site, it still fails without a valid token or origin. -
Mature, well-understood patterns
Synchronizer tokens, double-submit cookies, andSameSiteare established approaches. -
Critical for session-based apps
If you have login cookies, you need this.
Cons
-
Adds implementation complexity
Tokens need issuing, storing, validating, and rotating properly. -
Can be awkward for SPAs if done poorly
Especially when backend and frontend are split and nobody planned the auth flow. -
SameSiteis helpful but not a full strategy by itself
Treat it as defense-in-depth, not your only protection. -
Doesn’t solve cross-origin API access
You still need proper CORS if browser JS is calling another origin.
When you need CORS
You need CORS when:
- browser JavaScript on one origin calls another origin
- your frontend and API are on different scheme/host/port combinations
- you want frontend code to read cross-origin responses or headers
Example:
https://app.example.comhttps://api.example.com
Different origin. You need CORS.
When you need CSRF protection
You need CSRF protection when:
- your app uses cookies for authentication
- authenticated browser requests can change state
- endpoints like POST, PUT, PATCH, DELETE do sensitive work
Examples:
- changing email
- creating invoices
- deleting data
- transferring money
- updating account settings
If the browser auto-attaches auth cookies, assume CSRF is on the table.
What about token-based auth?
If your API uses an Authorization: Bearer <token> header and the browser does not automatically attach it cross-site, CSRF risk is usually much lower.
That’s why many pure APIs avoid classic CSRF problems: there’s no ambient cookie auth for an attacker to exploit.
But don’t oversimplify this either:
- If you store tokens unsafely in the browser, you create other problems
- If you still use cookies anywhere, CSRF may still matter
- CORS is still needed for browser-based cross-origin access
Best practice: use both when needed
A lot of apps need both.
Here’s a common setup:
- SPA on
https://app.example.com - API on
https://api.example.com - session cookie auth
- browser frontend making credentialed API calls
You need:
- CORS to allow the frontend origin to call the API
- CSRF protection because cookies are sent automatically
- Sensible cookie settings like
Secure,HttpOnly, andSameSite - Other security headers as part of a broader baseline — if you’re reviewing those too, CSP Guide is useful for the CSP side of things
A typical CORS config for credentialed requests looks like this:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
And your CSRF defense might validate a token:
app.post("/api/email/change", (req, res) => {
const csrfToken = req.get("X-CSRF-Token");
const sessionToken = req.session.csrfToken;
if (!csrfToken || csrfToken !== sessionToken) {
return res.status(403).json({ error: "Invalid CSRF token" });
}
// perform state-changing action
res.json({ ok: true });
});
That’s the right mental model: CORS for cross-origin reads, CSRF protection for forged authenticated actions.
The short version
If you’re comparing CORS vs CSRF, don’t treat them like alternatives.
- CORS answers: “Can this origin read this response in the browser?”
- CSRF answers: “Can this request be trusted as intentionally made by the user?”
One is a browser sharing policy. The other is a request forgery defense.
Mixing them up leads to broken apps and fake confidence, which is my least favorite combo in security.