CORS in JavaScript: why the browser blocks your API call

CORS lets a page read a response from another origin. The error comes from the browser, and the fix always lives on the server being called.
4 min read
Believemy logo

The first call to an API hosted elsewhere almost always ends the same way: the console shows a blocking message, the network tab shows a response that did arrive, and the code cannot touch it.

This mechanism is neither a bug nor pointless strictness. It stops any random page from reading, with your cookies, the data of a site you are signed into.


Definition

CORS, short for cross-origin resource sharing, is the protocol through which a server allows a page from another origin to read its responses. The permission travels in HTTP headers, and the browser is what enforces it.

Nothing written on the page side unblocks it: a refused fetch() call is fixed on the server being called, or not at all.

Three CORS scenarios compared on the same time scaleA simple request costs one round trip. A preflighted request costs two: the OPTIONS call then the real one. Once the permission is cached, it is back to one.Preflight: one extra round trip before the requestEach block is one round trip to the serverSimple request: GET, no custom headersGET /api1 round tripPreflighted request: JSON POSTOPTIONSPOST /api2 round tripsNext call: the permission is cachedPOST /apitime saved1 round tripPreflight doubles the latency, caching removes it


What an origin is

An origin is the trio of protocol, domain and port. A single difference is enough to change origin, which the table shows across three columns for a page served from https://app.example.com.

URL being calledSame originWhat differs
https://app.example.com/apiYesNothing, only the path changes
https://api.example.comNoThe subdomain
http://app.example.comNoThe protocol
https://app.example.com:8443NoThe port

That second row surprises people often: a subdomain of the same site is still another origin, and therefore needs explicit permission.


The preflight request

Some requests go out directly. Others are preceded by an automatic OPTIONS call that asks for permission before sending anything. That detour kicks in as soon as a request steps outside the simplest frame: a method other than GET or POST, a custom header, or a body announced as application/json.

JAVASCRIPT
// On the server, the response has to carry these headers
function allow(response, origin) {
  response.setHeader("Access-Control-Allow-Origin", origin);
  response.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE");
  response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  response.setHeader("Access-Control-Max-Age", "86400");
}

The last header caches the permission, which spares an extra round trip before every call. Each browser applies its own ceiling, often far below the value you ask for.

Good to know

The block protects the reading of the response, not the server. A simple request really does reach its destination before being blocked at the reading stage: a write therefore still needs protection on the server, independently of this mechanism.


Frequently asked questions

Question

Why does the same call work from a REST client?

Because the rule is enforced by the browser, and by the browser alone. Neither a client such as Postman, nor a curl command, nor a Node.js script pays any attention to these headers. A call that answers fine from the command line and fails in the tab is therefore perfectly normal.


Question

Can the block be worked around from the page?

No, and that is by design. The mode: "no-cors" option lets the request go out but returns an opaque response whose body and status are both unreadable. The only lasting solution is to go through your own server, which calls the API and hands you the result from your own origin.


Question

Why does the star not work with cookies?

Because the * value is incompatible with a call that carries credentials. You then have to echo the caller's exact origin in Access-Control-Allow-Origin, add Access-Control-Allow-Credentials set to true, and set credentials to include on the page side. That setup is built step by step in the Node.js course.

Related terms

Discover our javaScript glossary

Every word of JavaScript explained simply: keywords, built-in objects, methods, errors and concepts. Clear definitions and examples that actually run, to learn and to troubleshoot.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.