What is the Same-Origin Policy?
Browsers enforce a rule: JavaScript on page A can only make network requests to the same origin (protocol + hostname + port). Without this policy, a malicious website could silently call your bank's API using your session cookies. CORS (Cross-Origin Resource Sharing) lets servers opt in to allowing specific cross-origin requests.
The CORS Flow
// Simple requests (GET/POST with basic headers):
Browser → GET https://api.example.com/data
Origin: https://app.example.com
Server → 200 OK
Access-Control-Allow-Origin: https://app.example.com
// Browser sees the header → allows JS to read the response ✓
// Preflighted requests (DELETE, PUT, or custom headers):
Browser → OPTIONS https://api.example.com/data
Access-Control-Request-Method: DELETE
Server → 204 No Content
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Max-Age: 86400 // cache preflight 24h
// Then the actual DELETE is sent
Server Configuration
// Express.js
app.use(cors({
origin: ['https://app.example.com'],
methods: ['GET', 'POST', 'DELETE', 'PUT'],
allowedHeaders: ['Authorization', 'Content-Type'],
credentials: true, // allow cookies
}));
// Manual headers (any server)
res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com');
if (req.method === 'OPTIONS') { res.status(204).end(); return; }
The Wildcard + Credentials Trap
// Works for public APIs (no cookies)
Access-Control-Allow-Origin: *
// REJECTED by browsers — wildcard + credentials is invalid:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
// Correct — must specify exact origin when using credentials:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
CORS is Browser-Only
CORS restrictions only apply to browser-based JavaScript. Server-to-server requests (curl, Node.js fetch, Postman) are never blocked by CORS. If an API call works in Postman but fails in the browser, it is a CORS configuration issue on the server — not a network or authentication problem.