HTTP

Header

Access-Control-Allow-Headers Header

Learn how Access-Control-Allow-Headers specifies which custom HTTP headers can be used during cross-origin requests in CORS preflight responses.

5 min read intermediate Try in Playground

TL;DR: Lists which HTTP headers are allowed in cross-origin requests. Custom headers like Authorization must be explicitly allowed in CORS preflight responses.

What is Access-Control-Allow-Headers?

The Access-Control-Allow-Headers header is used in CORS preflight responses to tell the browser which HTTP headers can be used in the actual cross-origin request. It’s like a bouncer at a club saying “These specific items are allowed inside.”

This header is part of the CORS (Cross-Origin Resource Sharing) mechanism that enables secure cross-origin requests from browsers.

How Access-Control-Allow-Headers Works

Browser sends preflight request:

OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, x-api-key
```http

**Server responds with allowed headers:**

```http
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: Content-Type, X-API-Key, Authorization
Access-Control-Max-Age: 86400

Browser then sends actual request:

POST /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Content-Type: application/json
X-API-Key: abc123

{"name": "John Doe"}
```http

## Syntax

```http
Access-Control-Allow-Headers: <header-name>
Access-Control-Allow-Headers: <header-name>, <header-name>, ...
Access-Control-Allow-Headers: *

Values

  • header-name - Specific header names allowed (case-insensitive)
  • * - Wildcard allowing any header (except Authorization and credentials headers)

Common Examples

Basic API Headers

Access-Control-Allow-Headers: Content-Type, Authorization
```text

Allows Content-Type and Authorization headers in cross-origin requests.

### Custom API Key

```http
Access-Control-Allow-Headers: Content-Type, X-API-Key, X-Request-ID

Allows custom headers for API authentication and request tracking.

Comprehensive API

Access-Control-Allow-Headers: Content-Type, Authorization, Accept, X-Requested-With, X-CSRF-Token
```text

Common set of headers for full-featured APIs.

### Wildcard (Non-Credentialed Requests)

```http
Access-Control-Allow-Headers: *

Allow any headers for public APIs (doesn’t work with credentials).

Real-World Scenarios

REST API with Authentication

# Preflight request
OPTIONS /api/posts HTTP/1.1
Host: api.blog.com
Origin: https://blog.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization

# Server response
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://blog.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-ID
Access-Control-Max-Age: 3600
```text

### GraphQL API

```http
# Preflight
OPTIONS /graphql HTTP/1.1
Access-Control-Request-Headers: content-type, x-apollo-operation-name

# Response
HTTP/1.1 204 No Content
Access-Control-Allow-Headers: Content-Type, X-Apollo-Operation-Name, X-Apollo-Tracing

File Upload with Custom Headers

# Preflight
OPTIONS /upload HTTP/1.1
Access-Control-Request-Headers: content-type, x-file-name, x-file-size

# Response
HTTP/1.1 204 No Content
Access-Control-Allow-Headers: Content-Type, X-File-Name, X-File-Size, X-Upload-ID
```javascript

## Server Implementation

### Express.js (Node.js)

```javascript
const express = require('express')
const cors = require('cors')
const app = express()

// Basic CORS with allowed headers
app.use(
  cors({
    origin: 'https://app.example.com',
    allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key']
  })
)

// Manual implementation
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com')
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key')

  if (req.method === 'OPTIONS') {
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
    res.setHeader('Access-Control-Max-Age', '86400')
    return res.status(204).end()
  }

  next()
})

app.post('/api/users', (req, res) => {
  // Handle the actual request
  res.json({ success: true })
})

Express with Dynamic Headers

app.use((req, res, next) => {
  const requestedHeaders = req.headers['access-control-request-headers']

  // Define allowed headers
  const allowedHeaders = ['Content-Type', 'Authorization', 'X-API-Key', 'X-Request-ID']

  if (requestedHeaders) {
    // Check if requested headers are allowed
    const headers = requestedHeaders.split(',').map((h) => h.trim())
    const permitted = headers.filter((h) => allowedHeaders.includes(h))

    res.setHeader('Access-Control-Allow-Headers', permitted.join(', '))
  } else {
    res.setHeader('Access-Control-Allow-Headers', allowedHeaders.join(', '))
  }

  next()
})
```javascript

### FastAPI (Python)

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

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.example.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["Content-Type", "Authorization", "X-API-Key"],
    max_age=3600
)

@app.post("/api/users")
async def create_user(user: dict):
    return {"success": True}

Django

# settings.py
CORS_ALLOWED_ORIGINS = [
    "https://app.example.com",
]

CORS_ALLOW_HEADERS = [
    'content-type',
    'authorization',
    'x-api-key',
    'x-request-id',
]

# Or using a custom middleware
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt

class CORSMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)

        response['Access-Control-Allow-Origin'] = 'https://app.example.com'
        response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-API-Key'

        if request.method == 'OPTIONS':
            response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
            response.status_code = 204

        return response
```nginx

### Nginx

```nginx
server {
    listen 80;
    server_name api.example.com;

    location /api/ {
        # Handle preflight
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' 'https://app.example.com';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-API-Key';
            add_header 'Access-Control-Max-Age' 86400;
            return 204;
        }

        # Handle actual requests
        add_header 'Access-Control-Allow-Origin' 'https://app.example.com';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-API-Key';

        proxy_pass http://backend;
    }
}

Best Practices

For Servers

1. Be specific about allowed headers

# ❌ Too permissive
Access-Control-Allow-Headers: *

# ✅ Specific and secure
Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-ID
```javascript

**2. Don't allow unnecessary headers**

```javascript
// ❌ Allowing too many headers
const allowedHeaders = ['*']

// ✅ Only what you need
const allowedHeaders = ['Content-Type', 'Authorization']

3. Use lowercase for consistency

# Header names are case-insensitive, but lowercase is conventional
Access-Control-Allow-Headers: content-type, authorization
```http

**4. Cache preflight responses**

```http
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

5. Validate requested headers

app.use((req, res, next) => {
  const requestedHeaders = req.headers['access-control-request-headers']
  const allowedHeaders = ['content-type', 'authorization', 'x-api-key']

  if (requestedHeaders) {
    const headers = requestedHeaders
      .toLowerCase()
      .split(',')
      .map((h) => h.trim())
    const allAllowed = headers.every((h) => allowedHeaders.includes(h))

    if (!allAllowed) {
      return res.status(403).send('Forbidden headers')
    }
  }

  next()
})
```text

### For Clients

**1. Only send headers you need**

```javascript
// ❌ Unnecessary headers trigger preflight
fetch('https://api.example.com/data', {
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value',
    'X-Another-Header': 'value'
  }
})

// ✅ Minimal headers
fetch('https://api.example.com/data', {
  headers: {
    'Content-Type': 'application/json'
  }
})

2. Handle preflight failures gracefully

fetch('https://api.example.com/data', {
  headers: {
    'X-API-Key': 'secret'
  }
}).catch((error) => {
  if (error.message.includes('CORS')) {
    console.error('CORS error: X-API-Key header not allowed')
  }
})
```text

## CORS Simple vs Preflighted Requests

### Simple Requests (No Preflight)

These headers don't trigger preflight:

```http
Accept
Accept-Language
Content-Language
Content-Type: application/x-www-form-urlencoded
Content-Type: multipart/form-data
Content-Type: text/plain

Preflighted Requests

These headers trigger preflight:

Content-Type: application/json
Authorization
X-API-Key
X-Requested-With
Any custom header
```text

## Common Header Combinations

### Basic API

```http
Access-Control-Allow-Headers: Content-Type, Authorization

API with Request Tracking

Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-ID, X-Correlation-ID
```text

### API with CSRF Protection

```http
Access-Control-Allow-Headers: Content-Type, Authorization, X-CSRF-Token

GraphQL API

Access-Control-Allow-Headers: Content-Type, Authorization, X-Apollo-Tracing
```text

### File Upload API

```http
Access-Control-Allow-Headers: Content-Type, X-File-Name, X-File-Size, X-Upload-ID

Testing Access-Control-Allow-Headers

Using curl (Simulating Preflight)

# Send preflight request
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: content-type, x-api-key" \
  -v
```javascript

### Using JavaScript

```javascript
// This will trigger a preflight if needed
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': 'secret'
  },
  body: JSON.stringify({ data: 'test' })
})
  .then((response) => console.log('Success'))
  .catch((error) => console.error('CORS error:', error))

Browser DevTools

// Open browser console and check Network tab
// Look for OPTIONS request before actual request
// Check response headers for Access-Control-Allow-Headers

Frequently Asked Questions

What is Access-Control-Allow-Headers?

This CORS header lists which request headers are allowed in cross-origin requests. Custom headers and Authorization trigger preflight and must be explicitly allowed.

Which headers need to be allowed?

Simple headers (Accept, Content-Language, Content-Type with simple values) work automatically. Allow custom headers like Authorization, X-Custom-Header explicitly.

Why is my Authorization header blocked?

Authorization always triggers preflight and must be listed in Access-Control-Allow-Headers. Wildcard * does not include Authorization for security reasons.

Can I use wildcard for headers?

Access-Control-Allow-Headers: * allows most headers but excludes Authorization and other sensitive headers. List sensitive headers explicitly even with wildcard.

Keep Learning