CORS Explained: Why Your Frontend Cannot Talk to Your API
Your React app calls your Express API. Chrome blocks the request. The error says "No Access-Control-Allow-Origin header." You add cors() and it works. But do you know what actually happened?
What CORS Is
Cross-Origin Resource Sharing. Browsers block requests from one origin (domain:port) to another by default. CORS headers tell the browser which cross-origin requests to allow.
Same origin: same protocol + domain + port. http://app.com:3000 and http://app.com:4000 are different origins.
Preflight Requests
For non-simple requests (PUT, DELETE, custom headers, JSON content-type), the browser sends an OPTIONS request first. If the server responds with the right CORS headers, the browser proceeds with the actual request.
OPTIONS /api/users HTTP/1.1
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization
Proper CORS Configuration
import cors from "cors";
app.use(cors({
origin: ["https://app.example.com", "https://admin.example.com"],
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
maxAge: 86400,
}));
Why origin: "*" Is Dangerous
Wildcard origin allows ANY website to call your API. An attacker hosts evil.com, user visits it while logged into your app, evil.com makes requests to your API with the users cookies. Use an explicit allowlist.
Also: credentials: true and origin: "*" cannot be used together. The browser rejects it.
Dynamic Origin Validation
For multi-tenant apps or dynamic subdomains:
app.use(cors({
origin: (origin, callback) => {
if (\!origin || /\.example\.com$/.test(new URL(origin).hostname)) {
callback(null, true);
} else { callback(new Error("Not allowed by CORS")); }
}
}));
Common Mistakes
- Using origin: "*" with credentials: Browser rejects this combination
- CORS on the frontend: CORS is a server-side configuration. You cannot fix it from React.
- Forgetting OPTIONS handler: Some frameworks need explicit OPTIONS route handling for preflight
- No maxAge: Without caching, browsers send a preflight for every request. Set maxAge to cache the preflight response.
CORS Is Not Security
CORS only applies to browsers. curl, Postman, and server-to-server calls ignore it entirely. CORS prevents browser-based attacks like CSRF, not API abuse. Always use authentication and rate limiting regardless of CORS.
Part of my Production Backend Patterns series. Follow for more practical backend engineering.
If this was useful, consider:
- Sponsoring on GitHub to support more open-source tools
- Buying me a coffee on Ko-fi
Top comments (0)