CORS Explained
Cross-Origin Resource Sharing (CORS) is the browser-side mechanism that allows a server to relax the same-origin policy and accept cross-origin requests under explicit conditions.
The same-origin policy
By default, scripts running on one origin (scheme + host + port) cannot read responses from a different origin. This blanket restriction prevents a malicious page from reading your bank balance via the cookies the browser would otherwise attach. CORS is the controlled escape valve.
Simple requests
A "simple" request — GET, HEAD, or a POST with only safelisted headers and a body of one of three media types (application/x-www-form-urlencoded, multipart/form-data, or text/plain) — is sent without a preflight. The browser includes an Origin header; the server must respond with Access-Control-Allow-Origin or the browser blocks the response from being read by the script.
Request:
GET /api/data HTTP/1.1
Origin: https://app.example.com
Response:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Preflighted requests
If a request uses a method other than GET/HEAD/POST, custom headers, or a non-simple content type, the browser first sends an OPTIONS preflight to check that the server allows it. Only if the preflight succeeds does the actual request go out.
Preflight:
OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Authorization, Content-Type
Preflight response:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Credentials and wildcards don't mix. If Access-Control-Allow-Credentials: true, the browser refuses Access-Control-Allow-Origin: *. You must echo the specific origin instead — and add Vary: Origin so caches don't mix responses.
Common pitfalls
- Forgetting to handle
OPTIONSat all — most servers don't auto-respond to preflight. - Setting CORS headers in the application but not on error responses — preflight failures often happen there.
- Using
Access-Control-Allow-Origin: *with cookies — silently broken. - Not including
Vary: Originwhen echoing the origin — caches will serve the wrong response.