Difference between `Access-Control-Allow-Origin: *` (wildcard) and specific origins

  • I have a mostly public API with some parts of it "credentialed" behind cookies, similarly to e.g. how WordPress' REST API works. (In our case, it's a GraphQL API but that shouldn't matter.)

    I want to enable CORS for it and am considering two options:

    Option 1:

    Access-Control-Allow-Origin: <dynamically return the incoming `Origin` header>
    Access-Control-Allow-Credentials: true

    Option 2:

    Access-Control-Allow-Origin: *

    (Plus other headers like Access-Control-Allow-Methods in both cases.)

    For what I know, both are secure – no matter what the CORS headers say, browser's cookie policy will apply and since we only use SameSite cookies, I think even if we serve this to https://evil.com, all should be OK:

    Access-Control-Allow-Origin: https://evil.com
    Access-Control-Allow-Credentials: true

    There's no real problem – the evil site will be able to read the public parts of the API (which they can already do via curl, for example) but they won't be able to abuse a browser of a specific user that would be "logged in" to the API – cookies for our "site" will not be sent.

    WordPress also uses Option 1 (for many years) and it's secure enough – see e.g. this Trac ticket.

    Still, I wonder why not use a wildcard – it's simpler code (no need to dynamically read the Origin header), no need to worry about setting related headers properly (Vary: Origin) etc. The wildcard just seems simpler.

    So my question is, why would I choose Option 1 for our API? What useful scenario does it unlock that wildcard prevents?

  • TL;DR

    Option 2 simply doesn't work, and option 1 is insecure.

    Simply hardcode the origin that you trust in the CORS response header.

    More details

    Option 2

    As Stephen Ullrich pointed out in his comment, Option 2 simply doesn't work because the Fetch standard (which defines how CORS works) instructs browsers to reject this combination of headers. See this relevant passage of the MDN Web Docs about CORS:

    When responding to a credentialed request, the server must specify an origin in the value of the Access-Control-Allow-Origin header, instead of specifying the "*" wildcard.

    Option 1

    As for Option 1, it was very insecure before major browsers changed the SameSite default value to Lax (and many browsers still haven't made that change). But even if the session-identifying cookie is set with SameSite=Lax or SameSite=Strict and all yours users have modern browsers, Option 1 remains quite insecure. You write

    There's no real problem – the evil site [...] won't be able to abuse a browser of a specific user that would be "logged in" to the API – cookies for our "site" will not be sent.

    Do not misconstrue the SameSite attribute as a license to blindly accept any origin from a credentialed CORS request! The problem is that SameSite only applies to cross-site requests, not to all cross-origin requests. To convince yourself that this subtlety matters, see

    Can you guarantee that the subdomains (or sibling domains) of the origin that sets the session-identifying cookie will never have any XSS or HTML-injection vulnerability, or that they won't ever be taken over by some malicious actor? If the answer is "no" (and it most likely is "no"), I would strongly advise against Option 1.

Suggested Topics