CORS Security

CORS Wildcard + Credentials — The Security Hole Developers Keep Making

Updated April 2026

Reading this article? Verify your fix in real-time. Test your CORS credentials config → CORSFixer

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.

Browser Console
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'. Origin 'https://yourapp.com' is therefore not allowed access.

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:

  1. evil.com makes a fetch to bank.com with credentials: include
  2. The browser sends the bank session cookie automatically
  3. The browser reads the response (because * allows any origin)
  4. 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.

Test your CORS credentials config → CORSFixer