CORS Wildcard + Credentials — The Security Hole Developers Keep Making
Updated April 2026
The combination of Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true is explicitly forbidden by the browser spec. Not because of a bug — because allowing it would be a critical security flaw.
Why the browser blocks it
Imagine a malicious site at evil.com. The user is logged into your bank at bank.com. If * were allowed with credentials:
- evil.com makes a fetch to bank.com with credentials: include
- The browser sends the bank session cookie automatically
- The browser reads the response (because * allows any origin)
- evil.com reads the user's bank account data
This is why * with credentials is forbidden. The browser is protecting your users — not breaking your app.
The fix — explicit origin
# Express
app.use(cors({ origin: "https://yourapp.com", // explicit, not * credentials: true
}));
# FastAPI
app.add_middleware(CORSMiddleware, allow_origins=["https://yourapp.com"], # not ["*"] allow_credentials=True, allow_methods=["*"], allow_headers=["*"],
)
# Nginx
add_header Access-Control-Allow-Origin "https://yourapp.com" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Vary Origin always;
Multiple origins with credentials
const ALLOWED = ["https://yourapp.com", "https://staging.yourapp.com"];
app.use(cors({ origin: function(origin, callback) { if (!origin || ALLOWED.includes(origin)) { callback(null, origin); // return the actual origin, not * } else { callback(new Error("Not allowed by CORS")); } }, credentials: true
}));
// Always add Vary: Origin when using dynamic origins
app.use((req, res, next) => { res.vary("Origin"); next(); });
When does credentials mode activate?
The browser sends credentialed requests (and enforces this rule) when your frontend uses:
// Any of these triggers credential mode
fetch(url, { credentials: "include" });
fetch(url, { credentials: "same-origin" }); // only for same origin
axios.defaults.withCredentials = true;
$.ajax({ xhrFields: { withCredentials: true } });
The Vary: Origin header
When you return specific origins instead of *, add Vary: Origin to tell CDNs to cache separate responses per origin. Without it, a CDN might cache a response with origin: https://app.com and serve it to a different user from a different origin.