CORS Explained: Access-Control-Allow-Origin, Credentials and Common Mistakes
CORS is a browser-enforced exception to the same-origin policy. Here's how it works and the one combination that goes wrong constantly.
By default, a browser won't let JavaScript running on one origin read a response from a different origin - that's the same-origin policy. CORS is the mechanism a server uses to selectively lift that restriction for specific origins, and it's controlled entirely by a handful of response headers.
What CORS actually controls
It's worth being precise here: CORS doesn't stop a cross-origin request from being sent, and it doesn't stop the server from processing it. It only controls whether the browser lets the calling script read the response. The request happens either way - CORS is a client-side reading restriction, not a server-side access control mechanism.
The core headers
| Header | Controls |
|---|---|
| Access-Control-Allow-Origin | Which origin(s) are allowed to read the response |
| Access-Control-Allow-Credentials | Whether cookies/credentials can be included in the cross-origin request |
| Access-Control-Allow-Methods | Which HTTP methods are allowed cross-origin |
| Access-Control-Allow-Headers | Which request headers the client is allowed to send |
| Access-Control-Expose-Headers | Which response headers JavaScript is allowed to read |
| Access-Control-Max-Age | How long the browser can cache a preflight response |
The preflight request
For requests beyond simple GET/POST with standard headers, the browser first sends an OPTIONS request (the "preflight") asking the server what's allowed, before sending the actual request. The server responds with the Access-Control-* headers describing what it permits, and the browser only proceeds with the real request if the actual method and headers fall within that response.
Public APIs: a wildcard is often fine
Access-Control-Allow-Origin: *For a genuinely public, unauthenticated API - one that returns the same non-sensitive data regardless of who's asking - a wildcard origin is a reasonable, intentional choice. It just means the response is treated as public and readable by any site.
The dangerous combination
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: trueBrowsers actually reject this exact pairing outright - a wildcard origin can't be combined with credentials. But a server that's configured to send this combination is a strong signal of a deeper problem: it's very likely dynamically reflecting whatever Origin header the request sent, rather than validating it against a real allow-list. If that reflection logic slips past the credentials restriction in any code path, it would let any website make authenticated, cookie-carrying requests to the API and read the response - effectively bypassing the same-origin policy for a logged-in user.
The correct pattern for credentialed CORS
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: trueThis is the right shape: the server validates the incoming Origin against a specific allow-list, and echoes back only that exact, validated origin - never a wildcard - whenever credentials are involved.
Common mistakes
- Reflecting the request's Origin header verbatim without validating it against an allow-list - functionally equivalent to a wildcard for anyone who can control what Origin they send.
- Allowing every HTTP method (Access-Control-Allow-Methods: *) when the endpoint only needs GET and POST.
- Assuming CORS is a server-side access control mechanism - it isn't; a request from a non-browser client (curl, a backend service) bypasses it entirely, since CORS is enforced by the browser, not the server.
- Setting Access-Control-Allow-Origin to * on an endpoint that returns user-specific or sensitive data, treating it as public when it isn't.
Checking your CORS configuration
Paste your response headers into the HTTP Security Headers Analyzer to check for the wildcard-plus-credentials combination and other overly broad CORS settings, alongside the rest of your security header configuration.
Summary
CORS controls whether a browser lets cross-origin JavaScript read a response - nothing more. A wildcard origin is fine for genuinely public data; anything credentialed needs a validated, specific origin, never a wildcard or a blindly reflected value.
Frequently asked questions
- Is CORS a security header?
- Not a single header - CORS is a browser mechanism controlled by several related headers that determine whether a script running on one origin can read a response from another. Misconfiguring it can expose data cross-origin.
- Why is Access-Control-Allow-Origin: * with credentials dangerous?
- Browsers actually block this exact combination outright, but a server configured to send it is very likely dynamically reflecting the request's Origin header instead of validating it against an allow-list - which, if it slips past the credentials check, would let any site make authenticated requests and read the response.
- Does CORS protect my API from being called by other sites?
- No - CORS only controls whether a browser lets JavaScript on another origin read the response. It doesn't prevent the request from being made at all (a server still receives and can process it), and it does nothing to stop non-browser clients like curl or a server-to-server call.
Try it yourself
The tool that pairs with this guide.
Did this help you tighten your security headers?
If these free developer tools save you time, consider buying me a byte. Your support helps keep DevCraftKit free and growing.
Related articles
HTTP Security Headers Explained: A Practical Developer Guide
A handful of response headers tell the browser how to defend the page they came with. Here's what each one actually does.
Published
Content-Security-Policy (CSP) Explained With Examples
CSP is an allow-list for where your page's resources can load from. Here's how the directives fit together.
Published
What Is HSTS? Strict-Transport-Security Explained
HSTS closes a narrow but real window for downgrade attacks. Here's exactly what it enforces and how to configure it.
Published