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

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 .

Try it

Related guides

Newest guides