- Home
- HTTP Headers
- Access-Control-Allow-Headers Header
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.
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
Related Headers
- Access-Control-Allow-Origin - Specifies allowed origins
- Access-Control-Allow-Methods - Specifies allowed HTTP methods
- Access-Control-Request-Headers - Client requests permission for headers
- Access-Control-Max-Age - How long to cache preflight response
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.