Understanding CORS Header Preflight Requests
How browsers enforce the same-origin policy, why CORS preflight OPTIONS requests exist, and how to configure correct server-side CORS headers.
Introduction
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that restricts web pages from making requests to a domain different from the one that served the page. When your frontend at https://app.example.com calls your API at https://api.example.com, the browserβs same-origin policy would normally block this. CORS is the protocol that allows servers to declare βI allow requests from this other originβ β without it, cross-origin APIs would be inaccessible from browsers (though not from curl or server-to-server calls).
Step-by-Step: How CORS Works
flowchart TD
A["1. What Triggers CORS"]
B["2. The Preflight Request"]
C["3. Server Response (Correct CORS Headers)"]
D["4. Express.js CORS Configuration"]
E["5. Common CORS Mistakes"]
A --> B
B --> C
C --> D
D --> E
Step 1: What Triggers CORS
A cross-origin request occurs when any of these differ between page origin and request target: protocol (http vs https), domain (app.example.com vs api.example.com), or port (example.com:3000 vs example.com:8080).
Not all cross-origin requests are treated equally:
Simple requests (no preflight):
- Methods: GET, POST, HEAD
- Headers: only Content-Type (text/plain, multipart/form-data, application/x-www-form-urlencoded)
Non-simple requests (require preflight):
- Methods: PUT, PATCH, DELETE, OPTIONS
- Custom headers: Authorization, X-Api-Key, Content-Type: application/json
- Most REST API calls with JSON bodies
Step 2: The Preflight Request
Before sending the actual request, the browser automatically sends an OPTIONS preflight to ask the server if the real request is allowed:
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization, Content-Type
Step 3: Server Response (Correct CORS Headers)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Api-Key
Access-Control-Max-Age: 86400
Access-Control-Allow-Credentials: true
Allow-Origin: Which origins are permitted (*= all, but cannot be used with credentials)Allow-Methods: Which HTTP methods are allowedAllow-Headers: Which request headers the browser may sendMax-Age: How long the browser can cache this preflight result (seconds)Allow-Credentials: Whether cookies/auth headers are allowed (requires non-wildcard Allow-Origin)
Step 4: Express.js CORS Configuration
const cors = require('cors');
// Production CORS setup
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://app.example.com',
'https://admin.example.com',
process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null
].filter(Boolean);
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Authorization', 'Content-Type', 'X-Api-Key'],
credentials: true,
maxAge: 86400 // 24 hours preflight cache
};
app.use(cors(corsOptions));
app.options('*', cors(corsOptions)); // Enable pre-flight for all routes
Step 5: Common CORS Mistakes
β Mistake: Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true
Result: Browser blocks request β credentials require a specific origin
Fix: Set specific origin instead of wildcard
β Mistake: Missing preflight handler (OPTIONS method not handled)
Result: 405 Method Not Allowed on preflight β main request never sent
Fix: app.options('*', cors()) to handle all OPTIONS requests
β Mistake: CORS headers on error responses only
Result: Browser sees the 401/403 error without CORS headers β "Network Error" instead of error details
Fix: Add CORS middleware BEFORE auth middleware so headers are present on all responses
β Mistake: Proxying through a same-origin path to bypass CORS
Result: Works but hides the actual API URL from developers and adds latency
Better: Configure CORS properly on the API server
Key Takeaways
- CORS is a browser enforcement mechanism β server-to-server calls and curl are never subject to CORS restrictions.
- Preflight OPTIONS requests are automatically sent by browsers before non-simple cross-origin requests β your server must handle them and return correct headers.
Access-Control-Allow-Origin: *cannot be combined withAccess-Control-Allow-Credentials: trueβ credentials require a specific allowed origin.- Cache preflight responses with
Access-Control-Max-Ageto avoid OPTIONS requests on every API call. - CORS headers must be present on error responses too β place CORS middleware before authentication middleware.
Historical figures, architectures, and capabilities are for informational purposes only. Not technical, professional, legal, or financial advice. Sources: Research papers, developer documentation.