CORS (Cross-Origin Resource Sharing) is the browser mechanism that lets a page
on one origin make requests to a different origin in a controlled way. It works
through a set of request and response headers, plus an optional preflight
OPTIONS round-trip. This reference lists every header and explains the flow.
Complete header listing
Request headers (sent by the browser automatically)
| Header | When sent | Purpose |
|---|---|---|
Origin | Every cross-origin request | Identifies the requesting origin. The server uses this to decide whether to allow the request. |
Access-Control-Request-Method | Preflight only | Tells the server which HTTP method the real request will use. |
Access-Control-Request-Headers | Preflight only | Lists the custom headers the real request will include. |
Response headers (set by your server)
| Header | Required for | Purpose |
|---|---|---|
Access-Control-Allow-Origin | All cross-origin responses | The origin allowed to read the response — either a specific origin or *. |
Access-Control-Allow-Methods | Preflight response | HTTP methods the real request may use. |
Access-Control-Allow-Headers | Preflight response | Custom headers the real request may send. |
Access-Control-Allow-Credentials | Credentialed requests | When true, allows cookies and HTTP auth to be included. Incompatible with * origin. |
Access-Control-Expose-Headers | Responses with custom headers | Lists response headers JavaScript may read (beyond the default safelisted set). |
Access-Control-Max-Age | Preflight response | Seconds the browser may cache the preflight result before re-sending OPTIONS. |
Vary: Origin | Responses with specific origin | Tells caches the response varies by Origin, preventing one origin’s response being served to another. |
How it works
When JavaScript on https://app.example.com calls an API on
https://api.example.com, the browser attaches an Origin header. For
“non-simple” requests the browser first sends a preflight OPTIONS request
carrying Access-Control-Request-Method and Access-Control-Request-Headers.
The server must answer with matching Access-Control-Allow-* headers. Only if
the response approves the method, headers and origin does the browser send the
real request and expose the response to the script.
The preflight flow
Browser → OPTIONS /data
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type, x-token
Server → 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, PUT, DELETE
Access-Control-Allow-Headers: content-type, x-token
Access-Control-Max-Age: 600
If the response approves everything, the browser proceeds with the actual PUT.
Access-Control-Max-Age lets the browser cache the preflight to skip repeats.
Notes and common mistakes
Wildcard + credentials. Never combine Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true — browsers reject it. When you support credentials, echo the validated Origin from an allow-list and also add Vary: Origin so caches do not serve the wrong origin’s response.
Reflecting origin without validation. The most dangerous CORS misconfiguration is dynamically echoing whatever Origin the browser sends without checking it against an allow-list. Combined with Allow-Credentials: true this lets any malicious site make authenticated cross-origin requests and read the responses.
Missing Vary: Origin. If you echo a specific origin, CDNs and proxies can cache that response and serve it for requests from a different origin — a subtle but real correctness bug. Always include Vary: Origin when your Access-Control-Allow-Origin is dynamic.
Custom response headers not exposed. By default JavaScript can only read a handful of safelisted response headers. If your API returns X-RateLimit-Remaining or X-Request-Id, they must appear in Access-Control-Expose-Headers or the browser will return null when JS tries to read them.