CORS Explained

CORS (Cross-Origin Resource Sharing) is the browser's same-origin firewall. If your JS at site A wants to `fetch()` from site B, the browser asks B's permission via CORS headers.

1 credit

Same-origin rule

  • Origin = scheme + host + port. `https://a.com:443` and `http://a.com:80` are different origins.
  • By default, browser JS can't read responses from cross-origin fetches.
  • CORS is a BROWSER check — servers always receive the request. It's the RESPONSE that gets blocked client-side.
  • This doesn't apply server-to-server (no browser involved) or `<img>` / `<link>` / `<script>` — those have separate rules.

Simple requests

  • GET / POST / HEAD with default headers and `Content-Type` of `text/plain`, `application/x-www-form-urlencoded`, or `multipart/form-data`.
  • Browser sends request directly; server responds with `Access-Control-Allow-Origin: <origin>` or `*`.
  • If header is absent/wrong, browser hides the response from JS.

Preflighted requests

  • Triggered by: PUT/DELETE, custom headers (Authorization, X-*), JSON content type.
  • Browser sends `OPTIONS` first. Server must respond with allowed methods/headers.
  • Successful preflight gets cached per `Access-Control-Max-Age` (3600 is reasonable).

Server headers

6 items
Access-Control-Allow-Origin
`*` (anyone) or exact origin (echo if allow-listed)
Access-Control-Allow-Credentials
`true` if you send cookies; CAN'T combine with `*` origin
Access-Control-Allow-Methods
Preflight response: `GET, POST, PUT, DELETE`
Access-Control-Allow-Headers
Preflight response: `Content-Type, Authorization`
Access-Control-Expose-Headers
Which response headers JS can read (everything else is hidden)
Access-Control-Max-Age
Seconds to cache the preflight

Gotchas

  • `Access-Control-Allow-Origin: *` + `credentials: include` fetch = spec-forbidden. Browser blocks.
  • Localhost vs 127.0.0.1 are different origins. Stick to one.
  • Wildcards (`https://*.mysite.com`) are NOT supported — you must echo the origin.
  • CORS errors have confusing messages; always check the actual response headers in devtools Network tab.

Further reading