Concept · The browser

Same-origin policy and CORS

The browser keeps pages from reading other origins’ responses; CORS headers are how a server opts in.

4 minCORSpreflightAccess-Control-Allow-Originorigin

An origin is scheme + host + port: https://app.example.com:443. Change any of the three and it's a different origin. Note that https://example.com and https://api.example.com are different origins.

The same-origin policy is the browser's core security rule: a page can send requests almost anywhere, but script can't read the response from another origin unless that origin allows it. Without the rule, any site you visit could fetch('https://yourbank.com/account') with your cookies attached and read your balance.

CORS: the server opts in

Cross-Origin Resource Sharing is a set of response headers through which a server tells the browser which other origins may read its responses:

http
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin

For anything beyond a "simple" request, such as PUT/DELETE, a JSON Content-Type, or custom headers like Authorization, the browser first sends a preflight:

http
OPTIONS /orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type, authorization

The server must answer with matching Access-Control-Allow-Methods and -Headers. Access-Control-Max-Age lets the browser cache the answer so it doesn't send a preflight every time.

Misconceptions

  • CORS doesn't protect your server. It protects users' browsers from malicious pages. curl, scripts and other servers ignore it completely. You still need authentication and authorization.
  • A CORS error doesn't mean the request didn't happen. A simple POST is sent and processed; the browser just hides the response. Protect state-changing endpoints against CSRF with SameSite cookies or tokens. See sessions vs tokens.
  • Don't reflect any Origin with credentials allowed. That turns the policy off for every site. Keep an allowlist of origins.
  • * can't be combined with credentials. The browser rejects that combination.

Often you don't need it

If the frontend and API share an origin, for example with the API under /api behind the same host or a dev-server proxy, there's no cross-origin request and nothing to configure. When you do need CORS, set it in one place: middleware or your API gateway.

The same-origin policy limits what other sites can read. XSS is the opposite problem: an attacker's script running inside your origin, where the policy doesn't help.

Learn it in depth