CORS
The browser rule that blocks your fetch — and why disabling it is the wrong fix.
30 minDifficulty 2/5browser · securityAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
A page at `app.example.com` tries to `fetch('https://api.other.com/data')` and the browser console shows "blocked by CORS policy" — the request often reaches the server fine, the server responds fine, and the BROWSER refuses to let the page's JavaScript read that response, because a different-origin request without explicit permission is exactly what CORS exists to block.
The mental model
CORS is a permission system enforced entirely by the BROWSER, not the server or the network — the server can always be reached, but the browser will only hand the response back to the page's JavaScript if the server explicitly said, via a response header, that this specific origin is allowed to read it. No header means no permission, and the browser blocks access to the response regardless of what it contains.
How it works
The Same-Origin Policy is the default; CORS is the mechanism for relaxing it
By default, a browser prevents JavaScript on one origin (scheme + domain + port) from reading responses from a different origin — this is the Same-Origin Policy, and it exists to stop a malicious page from silently reading your bank's API responses using your logged-in session cookies. CORS is the SERVER's way of explicitly opting certain other origins IN to being allowed, via response headers.
Access-Control-Allow-Origin is the core permission header
A response header `Access-Control-Allow-Origin: https://app.example.com` tells the browser that specifically THIS origin is allowed to read the response. `Access-Control-Allow-Origin: *` allows ANY origin — convenient, but it also means the response can't safely include anything tied to the requester's identity (like relying on cookies), which is a real constraint browsers enforce alongside it.
A preflight request checks permission before the real one, for certain requests
A 'non-simple' cross-origin request — one using a method like PUT/DELETE, or custom headers — triggers the browser to first send an OPTIONS request (the preflight) asking 'would you allow this?' before sending the actual request at all. Only if the server's preflight response grants permission does the browser proceed with the real request; otherwise it never leaves the browser.
CORS is not a server-side security control — it's enforced client-side, by browsers
A tool like `curl` or a server-to-server request completely ignores CORS headers — CORS is a rule that BROWSERS choose to enforce for pages they render, to protect the USER's browser session, not a mechanism that prevents a server from receiving or processing a request from anywhere. Disabling CORS on a server doesn't remove any actual security; it removes a protection specifically for browser-based clients.
The mechanism
For a cross-origin request requiring one, the browser sends a preflight OPTIONS request asking the server which origins, methods, and headers it allows. If the server's response permits this specific request, the browser sends the actual request. When the actual response arrives, the browser checks its Access-Control-Allow-Origin header against the page's own origin — only if they match (or the header is a wildcard) does the browser let the page's JavaScript access the response data.
sequenceDiagram participant B as Browser (app.example.com) participant S as Server (api.other.com) B->>S: OPTIONS /data (preflight, if needed) S->>B: Access-Control-Allow-Origin: app.example.com Note over B: permission granted B->>S: GET /data (actual request) S->>B: response + Access-Control-Allow-Origin header Note over B: JS is allowed to read this response
What people get wrong
- A CORS error means the request never reached the server.
- In most cases the server DID receive the request and DID respond — the browser simply refuses to hand that response to the page's JavaScript, because the server's response lacked the required permission header for this origin. This confuses developers checking server logs, who see the request logged as successfully received and handled, while the browser console still shows a CORS error — both observations are correct simultaneously.
- Adding Access-Control-Allow-Origin: * to every response is a harmless way to 'fix' CORS errors.
- A wildcard allows literally any origin to read the response, which is fine for genuinely public data but a real problem for anything containing user-specific or sensitive information — and browsers additionally refuse to combine a wildcard with credentialed (cookie-based) requests specifically to prevent this misuse. Treating CORS errors as an annoyance to silence with a wildcard, rather than a signal to configure permissions correctly, can accidentally expose data that should have been origin-restricted.
- Disabling CORS makes an API more secure by 'locking it down'.
- CORS restricts which BROWSER-based pages can read a response — it does nothing to prevent a non-browser client (a script, a server, curl) from accessing the API at all, so it's not a substitute for actual authentication and authorization. Relying on CORS as a security boundary is a category error — it's a browser-enforced convenience for protecting logged-in user sessions from malicious pages, not an access control mechanism for the API itself.
When not to use it
- The API is genuinely public and intended to be called from any website's JavaScript, with no user-specific data involved.
- `Access-Control-Allow-Origin: *` is appropriate and simple here — the wildcard's limitations (no credentialed requests) aren't a real cost for truly public, non-personalized data.
- The API needs to support cookie-based authentication from a specific known frontend origin.
- An explicit, specific `Access-Control-Allow-Origin` (never a wildcard) combined with `Access-Control-Allow-Credentials: true` — this is the only combination browsers permit for credentialed cross-origin requests.
Terms
- Same-Origin Policy
- — The browser's default restriction preventing JavaScript on one origin from reading responses from a different origin, which CORS provides a mechanism to relax.
- Origin
- — The combination of scheme, domain, and port that defines whether two URLs are considered 'the same' for same-origin policy purposes.
- Preflight request
- — An automatic OPTIONS request the browser sends before certain cross-origin requests, asking the server what's permitted before sending the actual request.
- Access-Control-Allow-Origin
- — The response header a server uses to tell the browser which origin(s) are permitted to read the response.
In an interview
A frontend developer says 'the API works fine in Postman but fails from the browser with a CORS error'. Why does this distinction make sense?
- Postman is not a browser and doesn't enforce the same-origin policy or CORS at all — it just sends the request and shows the response
- the browser enforces CORS specifically for pages it renders, checking the response's Access-Control-Allow-Origin header against the page's own origin
- this means the server IS working correctly; it's specifically missing the CORS header needed for browser-based JavaScript to read the response
Can you recall it?
Why can a request fail with a CORS error in the browser console even though the server successfully received and responded to it?
Connected ideas
Also part of
This idea matters in more than one area — which is usually why it matters.