Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
Vulnerability Research

Access-Control-Allow-Headers Wildcard: Is * Safe with Credentials?

Access-Control-Allow-Headers * wildcard: when it's allowed, when it's blocked by browsers, and how to configure CORS headers correctly for credentialed API requests.

Offensive360 Security Research Team — min read
CORS Access-Control-Allow-Headers wildcard cors credentials web security API security CWE-942 cross-origin access-control-allow-headers wildcard cors wildcard headers

Access-Control-Allow-Headers: * looks like a convenient catch-all for CORS header configuration — but its behavior is not the same as Access-Control-Allow-Origin: *, and combining it with Access-Control-Allow-Credentials: true produces a result that many developers find surprising. Understanding exactly what the wildcard does, when browsers block it, and how to configure allowed headers correctly will save you from both security vulnerabilities and hard-to-debug CORS errors.


What Access-Control-Allow-Headers Does

When a browser sends a preflight request (an HTTP OPTIONS request before a cross-origin request that uses custom headers, a non-simple method, or a non-simple content type), the server responds with CORS headers indicating what the actual request is permitted to send.

Access-Control-Allow-Headers tells the browser which request headers are allowed in the actual cross-origin request:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.yourcompany.com
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Max-Age: 86400

In this example, the browser is told it may send Content-Type, Authorization, and X-Requested-With headers in the actual request. Any other custom header (e.g., X-Custom-Token) would be blocked by the browser.


When Access-Control-Allow-Headers: * Is Allowed

The wildcard * in Access-Control-Allow-Headers was formally added in the Fetch specification update and is supported by all modern browsers. When used without credentials, it means “any request header is allowed”:

Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Methods: *

This is valid and works in modern browsers for unauthenticated, non-credentialed requests — the same scenarios where Access-Control-Allow-Origin: * is acceptable (public APIs, open data, CDN assets).


The Critical Exception: Wildcards Are Blocked with Credentials

The browser specification explicitly forbids wildcard values when Access-Control-Allow-Credentials: true is set. From the Fetch specification:

If credentials is true and header is *, return failure.

This means the following combination does not work and will be blocked by all modern browsers:

Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true

The browser will reject this response because wildcards and credentials cannot be combined. This is a deliberate security measure: if the browser allowed credentialed requests (which carry session cookies, Authorization headers, and TLS client certificates) to any origin with any header, it would enable widespread cross-site credential theft.

What the Browser Error Looks Like

In Chrome’s console, you’ll see something like:

Access to fetch at 'https://api.yoursite.com/data' from origin 'https://app.yoursite.com' 
has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header 
in the response is '' which must be 'true' when the request's credentials mode is 'include'.

Or alternatively:

Access to fetch at 'https://api.yoursite.com/data' from origin 'https://app.yoursite.com'
has been blocked by CORS policy: Cannot use wildcard in Access-Control-Allow-Origin when 
credentials flag is true.

The exact message varies depending on which wildcard combination is invalid, but the root cause is the same: credentials + wildcard is not permitted.


The Authorization Header Is Not Covered by Wildcard

There is an additional nuance specific to the Authorization header. Even when Access-Control-Allow-Headers: * is used in a non-credentialed context, the Authorization header is explicitly excluded from the wildcard match.

From the Fetch specification:

The Authorization HTTP header cannot be added using *; the Authorization header must be listed explicitly.

This means:

# Does NOT allow Authorization header in cross-origin requests
Access-Control-Allow-Headers: *

# Does allow Authorization header
Access-Control-Allow-Headers: *, Authorization

Or, more explicitly and safely:

Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-API-Key

This catches many developers by surprise — they set a wildcard thinking all headers are covered, then find that Authorization: Bearer <token> headers are still being blocked by the browser.


Secure CORS Header Configuration for API Servers

For APIs that require authentication (virtually all production APIs), the correct CORS configuration is:

Node.js / Express

const allowedOrigins = [
  'https://app.yourcompany.com',
  'https://yourcompany.com',
];

app.use((req, res, next) => {
  const origin = req.headers.origin;

  if (allowedOrigins.includes(origin)) {
    // Explicit origin — required when credentials are involved
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Vary', 'Origin');
    res.setHeader('Access-Control-Allow-Credentials', 'true');
  }

  if (req.method === 'OPTIONS') {
    // Explicit header list — wildcard blocked with credentials
    res.setHeader('Access-Control-Allow-Headers',
      'Content-Type, Authorization, X-Requested-With, X-API-Key'
    );
    res.setHeader('Access-Control-Allow-Methods',
      'GET, POST, PUT, PATCH, DELETE, OPTIONS'
    );
    res.setHeader('Access-Control-Max-Age', '86400');
    return res.sendStatus(204);
  }

  next();
});

Python / FastAPI

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://app.yourcompany.com",
        "https://yourcompany.com",
    ],
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
    allow_headers=[
        "Content-Type",
        "Authorization",
        "X-Requested-With",
        "X-API-Key",
    ],
    # Do NOT use allow_headers=["*"] with allow_credentials=True
)

Python / Flask

from flask import Flask, request
from flask_cors import CORS

app = Flask(__name__)

CORS(
    app,
    origins=["https://app.yourcompany.com", "https://yourcompany.com"],
    supports_credentials=True,
    allow_headers=[
        "Content-Type",
        "Authorization",
        "X-Requested-With",
    ],
    methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
)

Java / Spring Boot

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();

    // Explicit origins — wildcard not allowed with credentials
    config.setAllowedOrigins(List.of(
        "https://app.yourcompany.com",
        "https://yourcompany.com"
    ));

    // Explicit headers — wildcard blocks Authorization header
    config.setAllowedHeaders(List.of(
        "Content-Type",
        "Authorization",
        "X-Requested-With",
        "X-API-Key"
    ));

    config.setAllowedMethods(List.of(
        "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"
    ));

    config.setAllowCredentials(true);

    // Cache preflight response for 1 hour
    config.setMaxAge(3600L);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/api/**", config);
    return source;
}

The Vary: Origin Header Is Required

When you dynamically set Access-Control-Allow-Origin based on the request’s Origin header (the correct approach for credentialed requests), you must also include Vary: Origin in the response:

Access-Control-Allow-Origin: https://app.yourcompany.com
Vary: Origin

Without Vary: Origin, a CDN or reverse proxy cache may store the CORS response for one origin and serve it to requests from a different origin. This causes requests from other legitimate origins to fail, or — worse — causes CORS headers for the wrong origin to be served, creating both a security and reliability issue.


Testing Your CORS Header Configuration

Use curl to test your preflight response:

# Test preflight with a custom header
curl -X OPTIONS https://api.yoursite.com/endpoint \
  -H "Origin: https://app.yoursite.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Authorization, Content-Type" \
  -v

# Check the response for:
# Access-Control-Allow-Headers: (should include Authorization and Content-Type)
# Access-Control-Allow-Origin: https://app.yoursite.com (exact, not wildcard)
# Access-Control-Allow-Credentials: true

And test from an unexpected origin:

# Should receive NO CORS headers (or an error) — not a wildcard response
curl -X OPTIONS https://api.yoursite.com/endpoint \
  -H "Origin: https://evil.com" \
  -H "Access-Control-Request-Method: GET" \
  -v

A safe server should return no Access-Control-Allow-Origin header for unrecognized origins — not a wildcard.


What SAST Tools Detect

A static application security testing tool analyzing your server-side code should flag:

  1. Wildcard Access-Control-Allow-Origin on endpoints that handle authentication or return user-specific data
  2. Reflected origin — any code that copies the incoming Origin header value directly into Access-Control-Allow-Origin without validation
  3. Wildcard Access-Control-Allow-Headers: * combined with Access-Control-Allow-Credentials: true — which is both a specification violation and a security concern
  4. Missing Vary: Origin when the origin is set dynamically

In Offensive360, this maps to the CORSAllowOriginWildcard and CORSAllowCredentials rules in the Knowledge Base, which detect these patterns across Node.js, Python, Java, PHP, Go, and C# codebases.


Common Misconfiguration Patterns and Their Risks

Pattern 1: Wildcard With Credentials (Specification Violation)

// WRONG — browsers block this combination
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');

Risk: Browsers block the request, causing your application to break. If you encounter this in production code, it means credentialed cross-origin requests are failing for all clients.

Pattern 2: Reflected Origin Without Validation (Critical Security Issue)

// WRONG — any origin is reflected back, including malicious sites
const origin = req.headers.origin;
res.setHeader('Access-Control-Allow-Origin', origin); // No validation
res.setHeader('Access-Control-Allow-Credentials', 'true');

Risk: Any website can make credentialed requests to your API and read the responses, enabling cross-site data theft. This is functionally the most dangerous CORS misconfiguration.

Pattern 3: Wildcard Headers with Missing Authorization Header

// INCOMPLETE — wildcard doesn't cover Authorization per spec
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
// Authorization header is still blocked in cross-origin requests

Risk: JavaScript clients using Authorization: Bearer ... headers will fail for cross-origin requests even though the wildcard appears to allow everything.

Pattern 4: Correct — Explicit Allowlist

// CORRECT — explicit origins and explicit headers
if (allowedOrigins.includes(req.headers.origin)) {
  res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
  res.setHeader('Vary', 'Origin');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader('Access-Control-Allow-Headers',
    'Content-Type, Authorization, X-Requested-With'
  );
}

Summary: Access-Control-Allow-Headers Wildcard Rules

ConfigurationNon-Credentialed RequestsCredentialed Requests
Allow-Headers: * (no credentials)✅ Allowed
Allow-Headers: * + Allow-Credentials: true❌ Blocked by browsers❌ Blocked by browsers
Allow-Headers: * (covers Authorization?)❌ No — Authorization excluded
Allow-Headers: Content-Type, Authorization✅ Correct✅ Correct
Allow-Origin: * + Allow-Credentials: true❌ Blocked by browsers❌ Blocked by browsers
Explicit origin allowlist + Allow-Credentials: true✅ Correct✅ Correct

The rule: For any API that uses authentication, use an explicit origin allowlist and an explicit header list. Never use wildcards with Access-Control-Allow-Credentials: true. Always include Vary: Origin when the Access-Control-Allow-Origin value is set dynamically.


Offensive360 SAST detects wildcard CORS configurations, reflected-origin patterns, and credential/wildcard conflicts in your source code. See the full CORS vulnerability reference in the Knowledge Base or run a one-time scan for $500 to check your API configuration.

Offensive360 Security Research Team

Application Security Research

Find vulnerabilities before attackers do

Run Offensive360 SAST and DAST against your applications and get a full vulnerability report in minutes.