Skip to main content
Security
Published 10 min read

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

HeaderControls
Access-Control-Allow-OriginWhich origin(s) are allowed to read the response
Access-Control-Allow-CredentialsWhether cookies/credentials can be included in the cross-origin request
Access-Control-Allow-MethodsWhich HTTP methods are allowed cross-origin
Access-Control-Allow-HeadersWhich request headers the client is allowed to send
Access-Control-Expose-HeadersWhich response headers JavaScript is allowed to read
Access-Control-Max-AgeHow 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: true

Browsers 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: true

This 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.

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.

Buy me a byte 💾