CORS (Cross-Origin Resource Sharing) is a browser security feature that controls which websites can access your API. By default, browsers block requests from one origin to another - CORS headers tell browsers when to allow these cross-origin requests.
CORS
Cross-Origin Resource Sharing - A security mechanism that controls how web pages can request resources from different domains.
Why CORS Exists
Same-Origin Policy: Browsers block requests to different domains by default. This prevents malicious sites from stealing data via your logged-in sessions.
Origin = protocol + domain + port:
https://example.com:443 is different from http://example.com or https://api.example.com
CORS relaxes this restriction: Server sends headers saying "I allow requests from these origins."
How CORS Works
Simple requests (GET, POST with simple headers):
- Browser sends request with
Originheader - Server responds with
Access-Control-Allow-Origin - Browser checks if origin is allowed
- If allowed, JavaScript gets response. If not, error.
Preflight requests (PUT, DELETE, custom headers):
- Browser sends OPTIONS request first
- Server responds with allowed methods and headers
- If allowed, browser sends actual request
- If not, actual request is blocked
CORS Headers Explained
| Header | Purpose | Example |
|---|---|---|
Access-Control-Allow-Origin | Which origins can access | https://myapp.com or * |
Access-Control-Allow-Methods | Allowed HTTP methods | GET, POST, PUT, DELETE |
Access-Control-Allow-Headers | Allowed request headers | Content-Type, Authorization |
Access-Control-Allow-Credentials | Allow cookies/auth | true |
Access-Control-Max-Age | Cache preflight (seconds) | 86400 |
Access-Control-Expose-Headers | Headers JS can read | X-Total-Count |
Common CORS Configurations
Public API (no auth):
Access-Control-Allow-Origin: *
Anyone can call your API. Fine for public data.
Specific origins:
Access-Control-Allow-Origin: https://myapp.com
Only your app can call the API. Check origin dynamically for multiple allowed domains.
With credentials:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Required for cookies. Cannot use * with credentials.
Preflight Requests
Browsers send an OPTIONS preflight for:
- Methods other than GET, HEAD, POST
- Custom headers (like Authorization)
- Content-Type other than form-urlencoded, multipart, text/plain
Optimize with Max-Age:
Preflight adds latency. Cache preflight response:
Access-Control-Max-Age: 86400 (24 hours)
Common CORS Errors
"No 'Access-Control-Allow-Origin' header": Server didn't include CORS headers. Add them.
"Credential is not supported if the CORS header is '*'": Using credentials with wildcard origin. Specify exact origin.
"Method not allowed": Server didn't list the method in Allow-Methods.
"Header not allowed": Server didn't list the header in Allow-Headers. Add Authorization if using JWT.
Security Best Practices
Never reflect Origin header blindly: Don't copy request Origin to response. Validates against whitelist.
Be specific with origins:
* is fine for public APIs. Use explicit origins for private APIs.
Don't allow credentials with wildcard: Browsers block this anyway, but it signals misconfiguration.
Limit allowed methods: Only allow methods your API actually uses.
Validate origins server-side: CORS is browser-enforced. Server-to-server requests bypass it. Always authenticate.
CORS vs Authentication
CORS doesn't replace authentication: CORS controls browser requests. APIs are still accessible via curl, Postman, or server code. Always require authentication for protected endpoints.
CORS is not security by itself: It prevents malicious websites from using your users' sessions. It's not a general-purpose access control.
Common Mistakes
1. Adding CORS only to GET: OPTIONS preflight needs CORS headers too. Apply to all routes.
2. Forgetting Authorization header:
If using JWT, add Authorization to Access-Control-Allow-Headers.
3. Wildcard with credentials:
Access-Control-Allow-Origin: * with credentials fails. Use specific origin.
4. Not handling OPTIONS: Preflight requests need 200 response with CORS headers.
5. Development vs production:
localhost:3000 in dev, but production origin in prod. Configure per environment.
Debugging CORS
Browser DevTools: Network tab shows CORS headers. Console shows specific error.
Check preflight: Filter network tab for OPTIONS requests. Check response headers.
Test without browser: curl ignores CORS. If curl works but browser doesn't, it's CORS.
Code Examples
CORS Configuration in Express
import cors from 'cors';
// Simple: Allow all origins
app.use(cors());
// Specific origins
const allowedOrigins = [
'https://myapp.com',
'https://staging.myapp.com',
process.env.NODE_ENV === 'development' && 'http://localhost:3000'
].filter(Boolean);
app.use(cors({
origin: (origin, callback) => {
// Allow requests with no origin (mobile apps, curl)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, origin);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400 // Cache preflight for 24 hours
}));
// Manual CORS (without middleware)
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.setHeader('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
return res.status(200).end();
}
next();
});Related Terms
API Testing
The process of verifying that APIs work correctly, securely, and perform well.
REST API
An architectural style for building web APIs using HTTP methods and stateless communication.
Latency
The time delay between a request being sent and a response being received.
Mock Server
A fake server that simulates API responses for testing and development purposes.