Questions
Why does my API work in Postman but not in the browser?
Because Postman does not enforce the same-origin policy and a browser does. The request almost certainly reached your server and was processed in both cases — what differs is whether the client is allowed to read the response. This is CORS, and the fix is on the server: it has to state which origins may read its responses, not merely return the data.
The request succeeded; the read did not
A browser sends the cross-origin request, receives the response, and then refuses to hand it to your JavaScript because the server did not grant permission.
That is why the server logs show a normal 200 while the page reports a failure. Nothing failed on the server side.
The console message names the missing header explicitly, and reading it is faster than any other diagnostic step.
It also means CORS is not a security control for your API: the request already ran. Authentication is what protects it.
The same asymmetry explains why a cross-site form post can change data without the attacking page ever seeing the response — which is cross-site request forgery, and a separate problem from this one.
What the server has to send
`Access-Control-Allow-Origin` naming the requesting origin, or `*` for a public API. One origin or the wildcard — a list is not valid.
`Access-Control-Allow-Credentials: true` if the request carries cookies or authentication, and that combination is rejected with `*`, so the server must echo a specific origin.
`Vary: Origin` whenever the allowed origin is chosen per request, or a shared cache will serve one origin's permission to another.
`Access-Control-Expose-Headers` for any response header the page needs to read beyond the short default-safe list.
The preflight, and why it appears from nowhere
Some requests are preceded by an `OPTIONS` call asking permission, and the real request is never sent if the answer does not cover it.
A `Content-Type` of `application/json` triggers one, which is why almost every JSON API call preflights while a form post does not.
So does any method other than GET, HEAD or POST, and any custom or `Authorization` header.
A missing `OPTIONS` handler returning 404 or 405 is the most common cause: the preflight fails and the real request never happens.
The preflight response must not require authentication, or every cross-origin client fails before it can authenticate.
Reproducing it outside the browser
`curl -H 'Origin: https://app.example.com' -I https://api.example.com/resource` shows whether the allow-origin header comes back.
`curl -X OPTIONS -H 'Origin: https://app.example.com' -H 'Access-Control-Request-Method: PUT' -I https://api.example.com/resource` reproduces the preflight.
Vary the origin between two requests. A server that echoes whatever origin it receives is effectively a wildcard, and that is a real finding rather than a working configuration.
Postman can send the `Origin` header too, which turns it into a usable diagnostic rather than a misleading one.
Compare the preflight response against the real one. A server that allows the method on `OPTIONS` and rejects it on the actual request is a routing gap rather than a CORS gap.
The mistakes that follow this diagnosis
Echoing the request's origin unconditionally. It looks like an allow-list and permits every site on the internet to read authenticated responses.
Adding `*` to an API that returns personal data, which lets any page read it using a visitor's own session.
Disabling CORS in the browser with a flag or an extension. That fixes one developer's machine and nothing else.
Proxying through your own origin purely to avoid configuring CORS, which works and hides the missing configuration from everyone who comes later.
Frequently asked questions
- Does CORS protect my API?
- No. It restricts what browser pages may read. The request already reached the server, and any non-browser client ignores CORS entirely.
- Why does my request work with GET and fail with PUT?
- PUT triggers a preflight. If the OPTIONS handler is missing or does not list the method, the real request is never sent.
- Can I use a wildcard with cookies?
- No. Browsers reject Access-Control-Allow-Origin: * combined with credentials. The server must echo a specific, validated origin.
Sources
Related
VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.
Scan a website