# 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.

```text
No 'Access-Control-Allow-Origin' header is present on the requested resource
```

## Use 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
1. 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.

2. Add the header on your stack. Examples: Express with the `cors` middleware configured with your frontend origin, nginx with `add_header 'Access-Control-Allow-Origin' 'https://app.example' always;`, or your API gateway's CORS setting. The `always` in nginx matters: without it the header is missing on error responses.
   Expected: the header appears on responses from that endpoint.

3. Handle credentials correctly. If the frontend sends cookies or an Authorization header, the server must return the exact requesting origin (not `*`) plus `Access-Control-Allow-Credentials: true`, and the frontend fetch must set `credentials: 'include'`.
   Expected: credentialed requests succeed; the browser rejects any wildcard-plus-credentials combo, so do not ship that.

4. Verify from the command line the way the browser sees it:
   ```bash
   curl -s -D - https://example.com/api/things -H "Origin: https://app.example" -o /dev/null | grep -i access-control
   ```
   Expected: 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
