CORS Explained: How to Fix Cross-Origin Errors in Web Apps
Few errors frustrate web developers more than this one:
Access to fetch at 'https://api.example.com/data' from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.This guide explains what CORS is, why it exists, and exactly how to fix it.
The Same-Origin Policy
Browsers enforce the Same-Origin Policy (SOP) — a security mechanism that prevents JavaScript on one origin from reading responses from a different origin.
An origin is defined as: protocol + hostname + port.
| URL | Same origin as https://app.example.com? |
|---|---|
https://app.example.com/path | ✅ Yes |
http://app.example.com | ❌ No (different protocol) |
https://api.example.com | ❌ No (different subdomain) |
https://app.example.com:8080 | ❌ No (different port) |
SOP protects users: a malicious site can't use your logged-in session to make authenticated requests to your bank's API and read the response.
What CORS Is
CORS (Cross-Origin Resource Sharing) is a mechanism that lets servers opt in to allowing cross-origin requests. It works via HTTP headers that the server sends.
CORS doesn't block the request — the browser does. The server just tells the browser what's allowed.
Simple vs. Preflight Requests
Simple requests (GET, POST with plain text/form data) are sent directly, and the browser checks the response headers.
Preflighted requests (PUT, DELETE, POST with JSON, custom headers) trigger an automatic OPTIONS request first:
OPTIONS /api/data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, AuthorizationThe server must respond to this OPTIONS request with appropriate headers before the browser sends the real request.
The CORS Headers You Need
Server response headers:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400Allow-Origin— Which origins are allowed. Use*for public APIs (can't be combined with credentials).Allow-Methods— Which HTTP methods are allowed.Allow-Headers— Which request headers are allowed.Allow-Credentials— Whether cookies and auth headers are allowed (requires specific origin, not*).Max-Age— How long browsers cache the preflight response (seconds).
Fixing CORS in Common Backends
Node.js / Express
const cors = require('cors');
app.use(cors({
origin: ['https://app.example.com', 'https://www.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400
}));
// Handle preflight for all routes
app.options('*', cors());Python / FastAPI
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)Nginx
location /api/ {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
proxy_pass http://localhost:8080;
}Common Mistakes
Using * with credentials: You cannot use Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true together. Browsers reject this. Specify the exact origin.
Not handling OPTIONS: If your server doesn't respond to OPTIONS preflight requests, cross-origin requests with custom headers will silently fail.
Adding CORS headers in the frontend: CORS headers must come from the server. You cannot fix CORS from the browser.
Multiple origins: Access-Control-Allow-Origin only accepts one value (or *). To support multiple origins, check the request's Origin header server-side and echo it back if it's in your allowlist.
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
next();
});Debugging CORS
- 1Open browser DevTools → Network tab
- 2Find the failing request
- 3Check the response headers — is
Access-Control-Allow-Originpresent? - 4If it's an OPTIONS preflight, check if it returned 200/204
- 5Use
curlto verify server headers independently:
curl -I -X OPTIONS https://api.example.com/data -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: POST"Conclusion
CORS errors are a browser security feature, not a bug. Fix them server-side by returning the correct Access-Control-Allow-* headers. When deploying APIs on PandaStack, your containerized backend handles CORS configuration — the platform provides networking and HTTPS termination. See [docs.pandastack.io](https://docs.pandastack.io) for deployment guides.