Skip to content

Access-Control-Allow-Headers — Permitting Custom Headers in Cross-Origin Requests

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Access. We cover key concepts, practical examples, and best practices to help you master this topic.

The Access-Control-Allow-Headers response header lists the custom headers the browser is permitted to include in cross-origin requests, validated against the preflight Access-Control-Request-Headers value.

What You'll Learn

  • How custom headers trigger preflight and header validation
  • Wildcard and credentials interaction
  • Best practices for header whitelisting

Why It Matters

Custom headers like Authorization, X-API-Key, or X-Request-ID are essential for modern APIs. Misconfigured Allow-Headers blocks legitimate authenticated requests. Doda Browser's extension API uses custom headers for extension authentication and version negotiation.

sequenceDiagram
    Browser->>Server: OPTIONS /api/data
    Browser->>Server: Access-Control-Request-Headers: Authorization, X-Custom
    Server-->>Browser: Access-Control-Allow-Headers: Authorization, X-Custom
    Server-->>Browser: Access-Control-Allow-Origin: https://app.example.com
    Browser->>Server: GET /api/data
    Browser->>Server: Authorization: Bearer token123
    Browser->>Server: X-Custom: value
    Server-->>Browser: 200 OK

Code Examples

// Custom headers trigger preflight automatically
fetch('https://api.example.com/data', {
  headers: {
    'Authorization': 'Bearer token123',
    'X-API-Version': '2'
  }
});
// Browser sends Access-Control-Request-Headers:
// authorization, x-api-version
# Flask middleware for header validation
ALLOWED_HEADERS = {
    'Authorization', 'Content-Type', 'X-Request-ID',
    'X-API-Version', 'X-Idempotency-Key'
}

@app.after_request
def set_cors_headers(response):
    origin = request.headers.get('Origin')
    if origin in ALLOWED_ORIGINS:
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Headers'] = \
            ', '.join(sorted(ALLOWED_HEADERS))
        response.headers['Access-Control-Allow-Methods'] = \
            'GET, POST, PUT, DELETE, OPTIONS'
        response.headers['Vary'] = 'Origin'
    return response
# Simulate preflight with custom headers
curl -X OPTIONS https://api.example.com/data \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: authorization, x-api-version" \
  -I 2>&1 | grep -i "allow-headers"

Common Mistakes

1. Not Including Content-Type in Allow-Headers When Needed

If the browser sends Content-Type: application/json, it must be in Allow-Headers.

2. Using Case-Sensitive Header Names

Header names should be lowercase in Allow-Headers. The browser normalizes them.

3. Forgetting Authorization Header

Many APIs require Authorization but forget to include it in Allow-Headers.

4. Listing Too Many Headers

Each listed header increases preflight payload size and complexity. List only what you need.

5. Mixing Up Request and Response Headers

Allow-Headers is for request headers. Expose-Headers is for response headers JavaScript can read.

Practice Questions

  1. What request header does the browser send listing the custom headers it wants to include?
  2. Can Access-Control-Allow-Headers use a wildcard?
  3. Why must Content-Type be in Allow-Headers when sending JSON?
  4. Are header names case-sensitive in Allow-Headers?
  5. What happens if a requested header is not in Allow-Headers?

Answers:

  1. Access-Control-Request-Headers.
  2. Yes, modern browsers support the wildcard * for headers, but not with credentials.
  3. Because application/json is not a simple content type, the browser preflights and checks headers.
  4. No. Header names are case-insensitive and the browser normalizes them to lowercase.
  5. The browser blocks the actual request from being sent.

Challenge: Create an Express middleware that auto-detects which headers are used in requests and generates the minimal Allow-Headers list. Add a security warning when too many headers are allowed.

FAQ

What headers are always allowed without being listed?

Simple headers like Accept, Accept-Language, Content-Language, and Content-Type (with simple values) are always allowed and do not need to be listed in Allow-Headers.

Can I use * for Access-Control-Allow-Headers?

Yes, in modern browsers the wildcard * is supported for Allow-Headers. However, it cannot be used with Access-Control-Allow-Credentials: true.

Should I include custom response headers in Allow-Headers?

No. Allow-Headers controls request headers sent by the browser. Use Access-Control-Expose-Headers to control which response headers JavaScript can read.

How does the browser handle headers not in Allow-Headers?

The preflight fails and the browser does not send the actual request. An error is logged in the console describing the blocked headers.

Can I dynamically set Allow-Headers based on the endpoint?

Yes. You can configure different allowed headers per route. This is useful when different endpoints need different custom headers.

Mini Project

Build a header validation dashboard: an Express server that logs all Access-Control-Request-Headers from preflights, compares them against the configured Allow-Headers, and reports any mismatches. Include a configuration page to update the allowed headers list at runtime.

What's Next

Learn about Access-Control-Expose-Headers to control response header visibility, then study Access-Control-Allow-Credentials for credentialed requests.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro