CORS with credentials: Access-Control-Allow-Origin cannot be *
Sending cookies or HTTP auth across origins switches CORS into strict mode: the wildcard * is no longer allowed, and the server has to name the exact origin it trusts and say credentials are welcome.
The errors
Access to fetch at 'https://api.example.com/me' from origin 'https://app.example.com'
has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header
in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
... The value of the 'Access-Control-Allow-Credentials' header in the response is ''
which must be 'true' when the request's credentials mode is 'include'.
Firefox words it as Credential is not supported if the CORS header 'Access-Control-Allow-Origin' is '*'. Same cause.
Why * is refused
* means "any site may read this". That's fine for public data, but with credentials the response is personal: your logged-in account. If any site could read it, every page you visit could read your inbox. So the browser insists the server name one specific origin it trusts.
The three response headers
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
Vary: Origin stops a CDN or browser cache from serving one origin's answer to another. For preflighted requests (custom headers, JSON bodies, PUT/DELETE) the OPTIONS response needs the same origin and credentials headers, plus Access-Control-Allow-Methods and Access-Control-Allow-Headers, which also cannot be * with credentials. See CORS preflight failed.
Echo safely, from an allow-list
Blindly copying the request's Origin into the response is the same as * with credentials, which is a real security hole. Compare against a list.
// Express
const cors = require('cors');
const allowed = ['https://app.example.com', 'http://localhost:5173'];
app.use(cors({ origin: (o, cb) => cb(null, !o || allowed.includes(o)), credentials: true }));
# Flask
from flask_cors import CORS
CORS(app, origins=["https://app.example.com"], supports_credentials=True)
# FastAPI
app.add_middleware(CORSMiddleware, allow_origins=["https://app.example.com"],
allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
# (FastAPI expands * methods/headers for you; allow_origins must be explicit)
# nginx
map $http_origin $cors_ok { default ""; "https://app.example.com" $http_origin; }
add_header Access-Control-Allow-Origin $cors_ok always;
add_header Access-Control-Allow-Credentials true always;
add_header Vary Origin always;
The client side
fetch('https://api.example.com/me', { credentials: 'include' })
axios.get(url, { withCredentials: true })
new EventSource(url, { withCredentials: true })
Then the cookie itself must be allowed to travel
- If the front end and API are on different sites (say
app.vercel.appandapi.onrender.com), the cookie needsSameSite=None; Secure, and some browsers will still block it as a third-party cookie. Putting both on subdomains of one domain (app.example.com,api.example.com) makes them same-site, soSameSite=Laxworks and third-party blocking doesn't apply. - Subdomains of one domain are same-site but still cross-origin, so you still need the CORS headers above.
- The cookie must have been set by the API's own host (or with a parent
Domain). Paste your header into the Set-Cookie explainer and use "Would the browser send it?" with your two URLs.
Do you need credentials at all?
If the API uses a bearer token in an Authorization header instead of cookies, leave credentials at its default and the wildcard is fine for public endpoints. Token storage trade-offs are in localStorage vs cookies.
Related
CORS errors explained · CORS preflight failed · Why is my cookie not being set?
Questions
Why can't Access-Control-Allow-Origin be * with credentials?
Credentialed responses contain personal data. Allowing any origin would let every website read a logged-in user's data, so browsers require one named origin.
Can I just reflect the Origin header back?
Only after checking it against an allow-list. Reflecting any origin with credentials enabled lets any website read your users' data.
Do I need Vary: Origin?
Yes, whenever the Allow-Origin value depends on the request. Otherwise a cache can serve one origin's CORS answer to another and break or leak responses.
Can Allow-Headers or Allow-Methods be * with credentials?
No. With credentials the wildcard is treated literally, so list the methods and headers you allow.
Are subdomains of the same domain cross-origin?
Yes. app.example.com and api.example.com are different origins, so CORS applies, but they are the same site, so SameSite=Lax cookies still flow.
Published .