Fix: Credentials with Wildcard Origin Error
Diagnose and fix the common CORS error when using Access-Control-Allow-Credentials: true with Access-Control-Allow-Origin: *. Includes solutions for every major server framework.
Detailed Explanation
The Problem
You see this error in the browser console:
Access to fetch at 'https://api.example.com/data' 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'.
This happens when the server sends:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Why It Fails
The CORS specification (Fetch Standard) explicitly forbids this combination. Allowing any origin to make credentialed requests would be a severe security hole — any website could steal session cookies.
The Fix
Replace the wildcard with the exact requesting origin. The server must read the Origin request header and echo it back (after validating it against an allowlist).
Fix for Express.js
const cors = require("cors");
const allowedOrigins = ["https://app.example.com"];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
credentials: true,
}));
Fix for Nginx
map $http_origin $cors_origin {
default "";
"https://app.example.com" "$http_origin";
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Vary Origin always;
Fix for Apache
SetEnvIf Origin "^https://app\.example\.com$" CORS_ORIGIN=$0
Header always set Access-Control-Allow-Origin "%{CORS_ORIGIN}e" env=CORS_ORIGIN
Header always set Access-Control-Allow-Credentials "true"
Header always set Vary "Origin"
Quick Checklist
- Origin is not
* - Origin matches the
Originrequest header exactly (including protocol and port) -
Vary: Originis present - Client sets
credentials: "include"
Use Case
A developer adds credentials: true to their CORS middleware but forgets to change the origin from * to a specific domain. Everything works in Postman (which ignores CORS) but fails in the browser.