/errors/cors/wildcard-origin-with-credentials
CORS error: 'Access-Control-Allow-Origin' must not be the wildcard '*' when credentials mode is 'include'
- tool
- CORS
- versions
- Fetch Standard, Chrome, Edge, Firefox, Safari
- os
- any
The browser blocks a response with Access-Control-Allow-Origin: * when the request was sent with credentials (cookies, HTTP authentication or TLS client certificates). The Fetch standard forbids the wildcard for credentialed requests, so the server must name the origin exactly (Access-Control-Allow-Origin: https://app.example.com) and also send Access-Control-Allow-Credentials: true. Echo the Origin header only for origins on your allow list.
Reproduce it
A page on 127.0.0.1:4810 calls an API on 127.0.0.1:4811 (a different origin, because the port differs) that answers with the wildcard. Microsoft Edge runs headless through playwright-core; the console error is the one users see in DevTools:
npm init -y >/dev/null
npm install --silent --no-audit --no-fund playwright-core@1 >/dev/null 2>&1
cat > cors.js <<'EOF'
const http = require("http");
const { chromium } = require("playwright-core");
const api = http.createServer((req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Content-Type", "application/json");
res.end('{"ok":true}');
}).listen(4811, "127.0.0.1");
const web = http.createServer((req, res) => res.end("<!doctype html><title>app</title>")).listen(4810, "127.0.0.1");
(async () => {
const browser = await chromium.launch({ channel: "msedge", headless: true });
const page = await browser.newPage();
const errors = [];
page.on("console", (m) => { if (m.type() === "error") errors.push(m.text()); });
await page.goto("http://127.0.0.1:4810/");
const call = (opts) => page.evaluate((o) => fetch("http://127.0.0.1:4811/data", o).then((r) => "HTTP " + r.status, (e) => e.name + ": " + e.message), opts);
console.log("without credentials:", await call({}));
console.log("credentials include:", await call({ credentials: "include" }));
await new Promise((r) => setTimeout(r, 300));
console.log(errors.filter((e) => e.includes("CORS")).join("\n"));
await browser.close(); api.close(); web.close();
})();
EOF
node cors.jsThe same server works for the request without credentials. JavaScript only sees TypeError: Failed to fetch; the reason is printed in the console, not given to the page.
Fix it on the server
// Express with the cors package
const cors = require("cors");
app.use(cors({ origin: ["https://app.example.com", "http://localhost:5173"], credentials: true }));Without the package, the response to both the preflight and the real request needs:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: OriginVary: Origin stops caches from serving one origin's response to another. With credentials, Access-Control-Allow-Headers and Access-Control-Allow-Methods also lose their * meaning and must list names explicitly.