Fixing CORS Errors — Access-Control-Allow-Origin Explained

(Updated: July 16, 2026 ) CORS API JavaScript Security

A CORS error in the browser console means the response lacked permission headers the browser required for your JavaScript to read it — or the preflight failed. It does not mean axios is broken. This article is a hands-on fix guide: read the error, confirm headers, patch the server or edge, and stop applying folklore in the client.

For the related “works on localhost only” story, see the production-proxy angle in other posts — here we focus on making a correct CORS configuration.

What the browser is asking

When a page at https://app.example.com calls https://api.example.com, the browser sends an Origin header. For the SPA to read the response, the API must answer with something like:

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

If cookies or Authorization with credentialed mode matter:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

* plus credentials is illegal and will fail. Reflect an allowlisted origin instead.

Simple request vs preflight

“Simple” GETs with limited headers may skip OPTIONS. JSON POST with Content-Type: application/json usually triggers preflight:

OPTIONS /v1/items HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,authorization

Your server must respond with allow headers covering method and requested headers, then succeed on the real POST with ACAO again.

Express example

import cors from "cors";

const allowlist = new Set([
  "https://app.example.com",
  "http://localhost:5173",
]);

app.use(
  cors({
    origin(origin, cb) {
      if (!origin || allowlist.has(origin)) return cb(null, true);
      return cb(new Error("Not allowed by CORS"));
    },
    credentials: true,
  }),
);

Ensure error middleware still emits CORS headers; otherwise 500s become opaque CORS failures in DevTools.

nginx snippet

set $cors_origin "";
if ($http_origin ~* ^https://(app\.example\.com)$) {
  set $cors_origin $http_origin;
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Vary Origin always;

if ($request_method = OPTIONS) {
  add_header Access-Control-Allow-Methods "GET,POST,PUT,PATCH,DELETE,OPTIONS" always;
  add_header Access-Control-Allow-Headers "Authorization,Content-Type" always;
  add_header Access-Control-Max-Age 86400 always;
  return 204;
}

The always flag matters so error responses keep headers. Test carefully — if in nginx has footguns; many teams prefer the API app to own CORS.

Frontend checklist (what you can and cannot do)

ActionUseful?
Set mode: 'cors'Default for cross-origin reads; fine
Set credentials: 'include' when cookies neededYes, with matching ACAO
Add Access-Control-Allow-Origin from JSNo — response header only
Browser extension “Allow CORS”Dev theater; not a production fix
Proxy via your backendYes when you cannot change upstream

Debugging sequence

  1. Open Network → failed request → Response headers.
  2. If OPTIONS failed, fix preflight first.
  3. Compare Origin to Access-Control-Allow-Origin character-for-character.
  4. curl the OPTIONS and GET/POST with Origin set.
  5. Confirm CDNs do not strip ACAO or cache the wrong origin (Vary: Origin).
  6. Retry from the real app origin, not only Postman.
curl -i -X OPTIONS "https://api.example.com/v1/items" \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: content-type"

Security notes

Allowing * on credentialed cookie APIs is not just invalid — reflecting arbitrary origins on authenticated endpoints is dangerous. Maintain an allowlist. Prefer same-site architectures (BFF) when cookies are central.

Common misconfigurations

MistakeResult
Allowlist only localhostProd app blocked
ACAO on success onlyErrors look like CORS
WAF blocks OPTIONSPreflight dies
Trailing slash / www mismatchIntermittent failures

Bottom line

CORS is fixed on the server or edge by returning precise allow headers for your app’s origin, including preflight and error paths. Read the Network panel, curl OPTIONS, allowlist origins, and ignore client-side “CORS header” myths.