CORS explained

Why the browser blocks cross-origin reads, how preflight works, and the headers that fix it properly.

The problem CORS solves

Your browser attaches cookies and tokens to requests automatically. Without protection, any site you visited could read data from another service you are logged into — your bank, your mail. The same-origin policy blocks reading cross-origin responses; CORS is how a server explicitly relaxes that.

⚠️
CORS is a browser protection, not a security mechanism for your server. It constrains browsers — anything else (curl, scripts, servers) ignores it entirely. Authorization must still be enforced server-side.

Simple requests vs preflight

A request using only GET/HEAD/POST with simple headers is sent directly. Anything else — custom headers, PUT/PATCH/DELETE, JSON bodies — triggers a preflight OPTIONS request asking permission first.

OPTIONS /api/item HTTP/1.1
Origin: https://app.example
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: authorization
Access-Control-Max-Age: 600

The response headers

HeaderPurpose
Access-Control-Allow-OriginPermitted origin, or * (never with credentials)
Access-Control-Allow-CredentialsAllow cookies — requires an explicit origin
Access-Control-Allow-MethodsAllowed methods for preflight
Access-Control-Allow-HeadersAllowed request headers
Access-Control-Max-AgeCache preflight result (seconds)
Vary: OriginEssential when the origin varies per caller
⚠️
Access-Control-Allow-Origin: * combined with Allow-Credentials: true is rejected by browsers — a wildcard cannot authorise credentials. Echo the specific origin instead, and always send Vary: Origin.

Fixing it in practice

A CORS error is always fixed on the server that owns the resource. No amount of fetch options or browser flags will bypass it.

// Express: allow one trusted app
app.use(cors({
  origin: 'https://app.example',
  credentials: true
}));

// fetch side: include cookies when needed
fetch(url, { credentials: 'include' });
  • mode: 'no-cors' does not fix anything — it gives you an opaque response you cannot read.
  • During development, proxy through your dev server instead of opening your API to every origin.
  • A reverse proxy serving API and app on one origin removes CORS questions altogether.

FAQ

Why does it work in curl but fail in the browser?
curl does not implement the same-origin policy. If it works there and fails in the browser, the answer is almost always missing CORS headers.
Can I just use a browser extension?
It masks the problem for you only. Every real user still hits it — configure the server.

HTTP headers Async JavaScript and fetch

Last refreshed 2026-09-17.