No 'Access-Control-Allow-Origin' header is present on the requested resource
A fix guide for the missing Access-Control-Allow-Origin header: how this differs from preflight failures (it hits simple cross-origin requests), how to add the header server-side on common stacks, and the credentials-plus-wildcard trap. Use when cross-origin GETs or fetches fail in the browser but work in curl or Postman. Triggers: 'No Access-Control-Allow-Origin', 'CORS header missing'. Not for: preflight OPTIONS failures, CSP violations, or mixed-content blocks.
No 'Access-Control-Allow-Origin' header is present on the requested resource
TL;DR
The browser made a simple cross-origin request and the response came back without the Access-Control-Allow-Origin permission slip, so the browser refused to hand the response to your JavaScript. Add the header server-side with your frontend's exact origin. If the request uses cookies or an Authorization header, echo the specific origin and add Allow-Credentials instead of using a wildcard.
No 'Access-Control-Allow-Origin' header is present on the requested resourceUse this when
- Cross-origin fetch or XHR fails in the browser but the same URL works in curl
- A third-party widget or partner site cannot read your API's responses
- The error names the header directly rather than mentioning preflight
- It broke after moving the API to a new host, CDN, or gateway
Not for this skill when
- The failing request is an OPTIONS preflight (different fix)
- The console shows a CSP or mixed-content error
- The API itself returns an error status (fix that first)
Steps
- Confirm the header is really absent. In devtools Network tab, click the failed request and inspect the response headers. Do not trust the server config; check what the browser received.
Expected: no access-control-allow-origin line in the response headers.
- Add the header on your stack. Examples: Express with the
corsmiddleware configured with your frontend origin, nginx withadd_header 'Access-Control-Allow-Origin' 'https://app.example' always;, or your API gateway's CORS setting. Thealwaysin nginx matters: without it the header is missing on error responses.
Expected: the header appears on responses from that endpoint.
- Handle credentials correctly. If the frontend sends cookies or an Authorization header, the server must return the exact requesting origin (not
*) plusAccess-Control-Allow-Credentials: true, and the frontend fetch must setcredentials: 'include'.
Expected: credentialed requests succeed; the browser rejects any wildcard-plus-credentials combo, so do not ship that.
- Verify from the command line the way the browser sees it:
curl -s -D - https://example.com/api/things -H "Origin: https://app.example" -o /dev/null | grep -i access-controlExpected: the output shows access-control-allow-origin with your origin. Then retest in the browser.
Variant: works for 200s but missing on errors
The classic nginx gotcha: add_header without always only applies to successful responses, so your error payloads fail CORS and the frontend cannot even read the error. Add always.
Variant: multiple allowed origins
The header takes a single origin, not a list. Echo back the request's Origin when it matches your allowlist, otherwise omit the header. Framework CORS middleware usually does this for you.
Variant: CDN caching responses without Vary: Origin
A cached response generated for one origin gets served to another without the right header. Add Vary: Origin so caches key correctly, or disable caching for the API.
Variant: wildcard origin with credentials
Developers set * for convenience, add credentials later, and everything breaks. The browser forbids the combination outright. Echo the origin instead.
Why this happens
The same-origin policy is the browser's default: a page may only read responses from its own origin unless the response explicitly opts in with Access-Control-Allow-Origin. Command-line tools do not enforce this, which is why the API looks fine in curl and Postman while the browser blocks it.
Edge cases and pitfalls
- Some browsers cache the CORS decision per URL. A fixed header can look broken until a hard refresh.
- The header must be on the actual response, not just the preflight. Both matter for non-simple requests.
- Allowing every origin (
*) on an API that serves user-specific data is a data-leak posture. Scope it to the frontends you own. - If a proxy sits in front of your app, confirm it forwards the header instead of stripping it. Check at the browser, not at the app.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_19MvK9oBvWmnosDzc4ZquQ
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.