Credentialed request
A credentialed request is a cross-origin fetch/XHR that carries credentials the browser manages for you: cookies, browser-managed HTTP authentication, or TLS client certificates. Browsers apply stricter CORS rules to these: no wildcards allowed, and the server has to explicitly opt in.
An Authorization header you set yourself in JavaScript does not make a request credentialed. It triggers a preflight and must be listed in Access-Control-Allow-Headers, but it works with Access-Control-Allow-Origin: *. Only credentials: 'include' (or withCredentials) rules out the wildcard.
How to make a request credentialed
// fetch: opt in via the credentials option
fetch('https://api.example.com/me', {
credentials: 'include' // send cookies for api.example.com
});
// XMLHttpRequest: set withCredentials
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/me');
xhr.withCredentials = true;
xhr.send();
By default fetch uses credentials: 'same-origin', which sends cookies on same-origin requests but not cross-origin. You have to ask for 'include' for credentialed cross-origin calls.
The stricter server-side rules
On a credentialed cross-origin response, the server must:
- Send
Access-Control-Allow-Credentials: true. (Without it, the browser drops the response.) - Send a specific origin in
Access-Control-Allow-Origin, not*. The browser refuses the wildcard with credentials. - Not use
*inAccess-Control-Allow-HeadersorAccess-Control-Allow-Methodseither. List them explicitly.
// Bad: browser will block the response
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
// Good
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
Why the wildcard is forbidden
If * were allowed with credentials, any malicious site could read your authenticated responses from any other site that opted into CORS with a wildcard. The combination would defeat the whole point of same-origin policy for credentialed requests. The browser rejects the configuration as a safety net.
Common gotchas
- Forgetting
credentials: 'include'on the client. Server gets no cookies, returns 401 for what should be a logged-in user. - Setting
Access-Control-Allow-Credentialswithout a specific origin. Browser silently drops the response. - The cookie itself needing
SameSite=None; Secure. Browsers don't sendSameSite=LaxorStrictcookies on cross-site requests, even credentialed ones. Cross-origin is not the same as cross-site:app.example.comcallingapi.example.comis cross-origin but same-site, soLaxcookies are still sent there. - Expecting a public CORS proxy to carry your session. corsproxy.dev, for example, answers with
Access-Control-Allow-Origin: *, never sendsAccess-Control-Allow-Credentials, and strips the browser'sCookieheader before calling the upstream.