Credentialed CORS requests forbid a wildcard origin — you must echo the exact origin
· cors · credentials · access-control-allow-origin · cookies · headers
corscredentialsaccess-control-allow-origincookiesheadersIf a cross-origin request includes credentials — cookies, HTTP authentication, or TLS client certificates, triggered by credentials: 'include' in fetch or withCredentials = true in XMLHttpRequest — the CORS specification forbids the wildcard Access-Control-Allow-Origin: *. The browser will reject the response even though the request reached the server. In credentialed mode the server must reflect the specific requesting origin in Access-Control-Allow-Origin and additionally return Access-Control-Allow-Credentials: true. A wildcard plus credentials is a contradiction the browser refuses to honor.
Why the wildcard is banned with credentials
The wildcard says "any origin may read this response." Combining that with the user's cookies would let any site on the web make authenticated requests to your API and read the results — exactly the cross-site attack CORS exists to prevent. So the rule is strict: when credentials are involved, the server must name the single origin it trusts. The correct response to a credentialed request from https://app.example.com is:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Note Access-Control-Allow-Origin carries one origin, not a list — the header has no syntax for multiple origins. To support several origins, the server reads the request's Origin header, checks it against an allowlist, and echoes back the matching value dynamically.
Wildcards are banned for headers and methods too
The same restriction extends beyond the origin. In credentialed mode, * loses its wildcard meaning in the other CORS response headers and is treated as the literal character. So Access-Control-Allow-Headers: * and Access-Control-Allow-Methods: * will not authorize your real headers and methods when credentials are sent — you must list them explicitly:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
The same logic applies to Access-Control-Expose-Headers: with credentials, a * there does not expose all headers, so name each response header you want the client to read.
The Vary: Origin caveat
Because the server now returns a different Access-Control-Allow-Origin depending on the incoming Origin, that response is no longer cacheable as one value for all clients. A shared cache or CDN could otherwise store the response generated for one origin and serve it to another, breaking CORS for the second origin or, worse, leaking the first origin's allowance. To prevent this, the server must add:
Vary: Origin
This tells caches to key the stored response on the request's Origin, so each origin gets the response computed for it. Whenever you dynamically echo the origin — which credentialed CORS forces you to do — Vary: Origin is mandatory.
Further reading
- No Access-Control-Allow-Origin header: how to fix it
- CORS preflight explained: when the browser sends an OPTIONS request
- WHATWG Fetch Standard — CORS protocol and the credentials rules