“Access to fetch at ‘https://api.example.com’ from origin ‘https://app.example.com’ has been blocked by CORS policy.” Almost every web developer meets this error. It makes sense once you understand what the browser is protecting.

The same-origin policy

An origin is the combination of scheme + host + port:

URL Same origin as https://app.example.com?
https://app.example.com/page Yes
http://app.example.com No (scheme)
https://api.example.com No (host)
https://app.example.com:8443 No (port)

Browsers let a page send requests anywhere, but by default JavaScript can’t read responses from other origins. Without this, any website you visit could use your logged-in cookies to read your email or bank pages through your browser.

CORS (Cross-Origin Resource Sharing) is how a server says: “I’m fine with JavaScript from these origins reading my responses.”

Simple requests

Some requests are sent directly: GET, HEAD or POST with “simple” headers and content types (application/x-www-form-urlencoded, multipart/form-data, text/plain). The browser adds an Origin header and checks the response:

Request:   Origin: https://app.example.com
Response:  Access-Control-Allow-Origin: https://app.example.com

If the response’s Access-Control-Allow-Origin doesn’t match, the browser blocks JavaScript from reading it. (The request still reached the server — CORS isn’t a server-side access control.)

Preflighted requests

Anything else — PUT, PATCH, DELETE, Content-Type: application/json, custom headers like Authorization — triggers a preflight: an OPTIONS request asking permission first.

OPTIONS /api/items
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type

The server must answer with:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400

Only then does the browser send the real request. Max-Age lets the browser cache the permission.

Credentials (cookies)

By default, cross-origin fetch doesn’t send cookies. With credentials: 'include', the server must respond with:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://app.example.com    (an exact origin — not *)

The wildcard * is not allowed together with credentials. Cookies also need suitable SameSite attributes (SameSite=None; Secure for cross-site cookies).

Reading response headers

JavaScript can only read a few “safe” response headers by default. Expose others explicitly:

Access-Control-Expose-Headers: X-Total-Count, Content-Disposition

Common errors and what they mean

Browser message Fix
No Access-Control-Allow-Origin header Server doesn’t send CORS headers for this origin (or an error page lacks them)
…header contains multiple values CORS set twice (app and proxy) — choose one
…must not be the wildcard '*' when credentials mode is 'include' Return the specific origin
Response to preflight request doesn't pass access control check OPTIONS not handled, or returns a redirect/404/401
Request header field authorization is not allowed Add it to Access-Control-Allow-Headers

Tip: error responses (401, 500) also need CORS headers, or the browser hides the real error behind a CORS message.

Secure configuration

  • List allowed origins explicitly; don’t reflect any Origin back blindly — that effectively allows every site, including with credentials.
  • Use * only for truly public, cookie-less resources (public APIs, fonts, open data).
  • Add Vary: Origin when the response depends on the origin, so caches don’t mix them up.
  • Remember: CORS doesn’t stop curl, scripts or other servers. Protect APIs with authentication and rate limiting.

Or avoid CORS entirely

Serve the front end and API from the same origin — e.g. https://example.com and https://example.com/api/ via a reverse proxy. No cross-origin requests, no CORS. See deploying a React app with Nginx.

Configuration guides: CORS in Nginx. Related: API security best practices.

  • CORS
  • Web Development
  • Security