Access-Control-Allow-Origin
An HTTP response header the server sends to tell the browser which origin is allowed to read the response body. The browser refuses to expose the body to JavaScript unless this header is present and matches (or is the wildcard).
The values you'll use
Access-Control-Allow-Origin: *: any origin can read the response. Doesn't work for credentialed requests.Access-Control-Allow-Origin: https://app.example.com: exactly this one origin. The server typically reads the request'sOriginheader and echoes it back if it's on an allowlist.- (header absent): browser blocks the read. The default.
The spec also allows the literal value null, but it matches sandboxed iframes, file: pages and other opaque origins that any site can create, so you shouldn't use it. See why the null origin is dangerous.
You can't list multiple origins in this header, only one origin or *. To allow several, the server has to inspect the request's Origin and reflect a matching value.
corsproxy.dev always sends Access-Control-Allow-Origin: * and never sends Access-Control-Allow-Credentials, so credentialed requests through it are not supported.
When you also need Vary: Origin
If your server reflects different Access-Control-Allow-Origin values depending on the request, add Vary: Origin to the response so caches don't serve a value meant for one origin to a different origin. Without it, CDNs and browser caches will silently break CORS for some requests.
Example: doing it right
// Node / Express: reflects the request origin if it's on an allowlist
const ALLOWED = new Set([
'https://app.example.com',
'http://localhost:3000'
]);
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');
}
next();
});
Common mistakes
- Sending
*with credentialed requests. The browser refuses the combination. If you send cookies or other browser-managed credentials across origins, you must echo a specific origin. (AnAuthorizationheader set in your own code doesn't count; it works with*once it's listed inAccess-Control-Allow-Headers.) - Forgetting
Vary: Origin. Caches will serve the wrong value to different origins. Subtle, intermittent failures. - Setting it on the upstream when the browser actually needs it on the preflight. If your real call gets a preflight, the OPTIONS response also needs the header, not just the final GET/POST.
- Trailing slashes and case.
https://example.comandhttps://example.com/are not the same string. Match exactly.