Wildcard vs Dynamic Origin Reflection: When to Use Each

Two server responses grant cross-origin access: a static Access-Control-Allow-Origin: *, and a dynamically reflected origin echoed back after validation. Choosing the wrong one either breaks credentialed requests or opens the API to every website on the internet. The decision is not a matter of taste — it follows directly from whether the endpoint handles credentials or caller-scoped data.

This page is part of CORS Security Auditing & Hardening, which covers the full process of finding and closing CORS misconfigurations.

The Decision in One Table

Scenario Correct choice Why
Public fonts, CDN assets, open read-only data, no credentials Access-Control-Allow-Origin: * Response is identical for all origins; simplest and most cacheable
Authenticated API using cookies or Authorization Reflect the validated origin + Allow-Credentials: true Wildcard is forbidden with credentials; only listed origins may read
Response contains per-tenant or per-user data (even without auth) Reflect the validated origin Limits which origins can read caller-scoped information
Multiple known front-end origins (app, admin, staging) Reflect the validated origin from an allowlist One wildcard cannot distinguish trusted from untrusted callers
Unknown / arbitrary origins with credentials Neither — reject Reflecting unvalidated origins is a credential-exfiltration vector

Root Cause: Why the Choice Is Forced

The WHATWG Fetch Standard (§3.2.3) makes one combination illegal: Access-Control-Allow-Origin: * cannot appear with Access-Control-Allow-Credentials: true. The browser checks this at response evaluation and blocks the read. So any endpoint that needs cookies or tokens cannot use a wildcard — it must echo the exact origin. Conversely, an endpoint that is genuinely public and credential-free gains nothing from reflection and is simpler and more cache-friendly with a wildcard, because the response body and headers are identical for every caller. The mechanics of this prohibition are detailed in Understanding Access-Control-Allow-Credentials.

Prerequisite State

Step-by-Step: Pick and Implement

Step 1 — Classify the endpoint

Ask two questions: does it carry credentials, and does it return caller-specific data? A “yes” to either rules out the wildcard.

Step 2a — Public + credential-free → wildcard

location /public/ {
  add_header Access-Control-Allow-Origin "*" always;
  # No Allow-Credentials, no Vary: Origin needed — response is identical for all
}

Step 2b — Credentialed or scoped → validated reflection

Match the origin against an allowlist and reflect only the matched value, adding Vary: Origin so caches key per origin:

const ALLOWED = new Set([
  'https://app.example.com',
  'https://admin.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('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Vary', 'Origin');
  }
  next();
});

For the allowlist architecture and subdomain-safe matching, see Dynamic Origin Validation Patterns.

Verification

# Public endpoint: wildcard is fine
curl -si https://api.example.com/public/data -H "Origin: https://anything.example" | grep -i access-control-allow-origin
# → access-control-allow-origin: *

# Credentialed endpoint: exact reflection, and deny for unknown origins
curl -si https://api.example.com/v1/me -H "Origin: https://app.example.com" | grep -i access-control
# → access-control-allow-origin: https://app.example.com  +  access-control-allow-credentials: true
curl -si https://api.example.com/v1/me -H "Origin: https://attacker.example" | grep -i access-control-allow-origin
# → (no header — correctly denied)

Security Boundary Note

Never reflect the Origin header without validating it against an allowlist. Unvalidated reflection behaves like a wildcard that also works with credentials, letting any website read authenticated responses — a direct data-exfiltration and CSRF-amplification vector. If you find yourself reaching for Access-Control-Allow-Origin: * on an endpoint that uses credentials, the answer is not a wildcard and not blind reflection; it is a validated allowlist. Deeper threat modelling is in Wildcard Risks & Mitigation.

Common Mistakes

Issue Technical impact Mitigation
Wildcard on a cookie-authenticated endpoint Browser blocks the credentialed response; the API appears broken Reflect the validated origin with Allow-Credentials: true
Reflecting Origin without an allowlist Any site can read authenticated responses Match against a trusted set before echoing
Reflection without Vary: Origin CDN serves one origin’s header to another; intermittent failures Add Vary: Origin to every reflected response
Wildcard on a per-tenant public endpoint Any origin can read another tenant’s data Reflect a validated origin even without credentials

FAQ

Is Access-Control-Allow-Origin: * ever safe?

Yes, for genuinely public resources that carry no credentials and expose no per-user or per-tenant data: public fonts, open datasets, CDN assets, and unauthenticated read-only APIs. There the wildcard is simpler and cache-friendly because the response is identical for every origin. It becomes unsafe the moment the endpoint relies on cookies or tokens or returns caller-scoped data.

Why can’t I use a wildcard with credentials?

The WHATWG Fetch Standard forbids Access-Control-Allow-Origin: * when Access-Control-Allow-Credentials is true. The browser rejects the response before JavaScript can read it, preventing any origin from reading an authenticated response. With credentials you must echo the exact validated origin string.

Is reflecting the Origin header the same as a wildcard?

Only if you reflect it without validation. Echoing whatever Origin arrives with no allowlist is effectively a wildcard that also works with credentials — the worst case, since any site can then read authenticated responses. Reflection is safe only when the origin is first matched against a trusted allowlist and the response includes Vary: Origin.