CORS explained: why it exists and how to fix it
same-origin policy, cross-origin resource sharing, preflight request, OPTIONS method, Access-Control headers, simple vs complex requests, credentials flag
CORS: Same-Origin Policy and Cross-Origin Sharing
CORS errors only appear in browsers — curl never triggers them. The browser enforces the same-origin policy on behalf of your users, not your server. Understanding this makes CORS errors predictable and fixable in minutes.
The same-origin policy
Browsers block JavaScript from reading responses from a different origin. Origin = scheme + hostname + port. https://app.example.com and https://api.example.com are different origins — the subdomain differs. So are http:// vs https:// on the same domain.
The preflight
For non-simple requests (custom headers, methods other than GET/POST, or a JSON body), the browser sends an OPTIONS preflight first:
# Preflight request
OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization, Content-Type
# Server must respond
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400Access-Control-Max-Age: 86400 caches the preflight for one day. Without it, every API request triggers an extra OPTIONS round-trip — doubling your latency for all API calls.
Credentials
Cookies and Authorization headers are not sent cross-origin by default. The client must set credentials: 'include' in the fetch call. The server must respond with Access-Control-Allow-Credentials: true and a specific origin — not a wildcard. Access-Control-Allow-Origin: * and credentials together are blocked by all browsers.
