Who Blocks the Request, and Why

The first misconception about CORS errors: "the server rejected my request." In reality, the browser refuses to hand the response to your JavaScript. Browsers enforce the Same-Origin Policy — scripts cannot read responses from another origin (the protocol + domain + port triple) — and CORS is the mechanism by which a server declares exceptions: "this origin may read me."

That is why the same request succeeds in curl or Postman and fails only in the browser: the policy lives in the browser. And it is why the fix always lives in the server's response headers.

Decoding the Three Classic Error Messages

Console message (gist)MeaningFix direction
No 'Access-Control-Allow-Origin' headerServer never declared CORS permissionAdd the header to server responses
Response to preflight request doesn't passThe OPTIONS pre-check was rejectedMake the server answer OPTIONS with 200 + CORS headers
... must not be the wildcard '*' when credentials mode is 'include'Wildcard used with cookie-bearing requestsName the origin explicitly + Allow-Credentials: true

When Does a Preflight Happen?

Simple requests (GET/POST with standard headers and plain content types) go straight out. But if any of the following applies, the browser first sends an OPTIONS request asking permission:

  • Sending Content-Type: application/json (which covers most real APIs)
  • Attaching custom headers such as Authorization
  • Using PUT, DELETE, or PATCH

In other words: a JSON API with token auth preflights essentially always. The most common server-side mistake is letting auth middleware answer OPTIONS with 401 — preflight handling must run before authentication.

The Server Header Set

HeaderExample valueRole
Access-Control-Allow-Originhttps://app.example.comAllowed origin (no * with credentials)
Access-Control-Allow-MethodsGET, POST, PUT, DELETEMethods declared in the preflight response
Access-Control-Allow-HeadersContent-Type, AuthorizationHeaders the client may send
Access-Control-Allow-CredentialstruePermits cookies/credentials
Access-Control-Max-Age86400Caches the preflight verdict (less OPTIONS traffic)

Whether these headers actually arrive is exactly what the HTTP header analyzer shows. Filtering for the OPTIONS request in DevTools' Network tab gives the same diagnosis.

Fixes to Avoid

  • Blanket * — unless the API is deliberately public, name your origins. It cannot be combined with cookie auth at all.
  • Disabling browser security — --disable-web-security turns off protection for every site in that browser. Shared with a team, it is an incident waiting to happen.
  • Random CORS proxies — your full request and response transit a third-party server. With auth tokens attached, you are handing over credentials.

The correct development workaround is your dev server's proxy (Vite, webpack-dev-server) making the API appear same-origin; production gets properly configured CORS headers. The broader header landscape is covered in the HTTP headers guide.

FAQ

Why does it work in Postman but fail in the browser?

Same-Origin Policy is a browser security model. Postman and curl are not subject to it — and that difference is itself the diagnostic proof of a CORS issue.

Server logs show the request succeeded. How is that possible?

The server did respond normally; the browser then refused to expose that response to your script. Blocking after receipt is precisely how CORS works.

Does CORS apply on localhost during development?

Yes — localhost:3000 and localhost:8080 differ in port, so they are different origins. A dev-server proxy is the standard answer.