Patterns to remove
- Access-Control-Allow-Origin set from the incoming Origin header without checking it against a list.
- Access-Control-Allow-Origin: * on an endpoint that returns private data.
- Allowing the null origin.
- Trusting any subdomain by pattern match, such as an endsWith check that also matches an attacker's look-alike domain.
An allow-list that works
const allowed = new Set(['https://app.example.com', 'https://www.example.com']);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (origin && allowed.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
next();
});What CORS will not do
CORS is enforced by browsers. A script, curl or another server can call your API directly, so the API must still check who is asking on every request.
Test it on your own API
curl -sI https://your-api.example/endpoint \
-H 'Origin: https://not-allowed.example' | grep -i access-controlFrequently asked questions
Is Access-Control-Allow-Origin: * always wrong?
No. It is fine for public data such as a public asset or open API. It is wrong for responses that depend on the user.
Why does my browser block the request but curl works?
CORS is a browser rule. It restricts what page scripts may read, not what the server accepts.