HTTP Headers: Request and Response Headers
HTTP headers are the unsung heroes of web communication. Every time your browser requests a resource or a server sends a response, headers carry crucial metadata that determines how that exchange...
Key Insights
- HTTP headers are metadata key-value pairs that control everything from authentication and caching to security and content negotiation—understanding them is essential for building robust web applications
- Request headers tell the server what the client wants and who it is, while response headers tell the client how to handle the data and enforce security policies
- Modern JavaScript provides powerful APIs like
fetchand the Headers interface for manipulating headers, but developers must understand header size limits, security implications, and CORS requirements to avoid common pitfalls
Introduction to HTTP Headers
HTTP headers are the unsung heroes of web communication. Every time your browser requests a resource or a server sends a response, headers carry crucial metadata that determines how that exchange behaves. Think of them as the envelope and routing information around a letter—the letter itself is the body, but the headers tell you who sent it, where it’s going, and how to handle it.
Headers come in two flavors: request headers (sent from client to server) and response headers (sent from server to client). Request headers might tell the server “I accept JSON data” or “Here’s my authentication token,” while response headers might say “This data expires in 5 minutes” or “Don’t embed this page in an iframe.”
Understanding headers is critical because they control authentication, caching, security policies, content negotiation, and CORS behavior. A misconfigured header can expose your application to security vulnerabilities or break critical functionality.
Here’s what a basic fetch request looks like, along with the headers it generates:
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data));
// In DevTools Network tab, you'll see request headers like:
// Accept: */*
// User-Agent: Mozilla/5.0...
// Accept-Encoding: gzip, deflate, br
Common Request Headers
Request headers tell the server what the client needs and provide context about the request. Here are the headers you’ll work with most frequently:
Content-Type specifies the media type of the request body. When sending JSON to an API, you’ll set this to application/json. For form submissions, it’s typically application/x-www-form-urlencoded or multipart/form-data.
Authorization carries authentication credentials, most commonly in the format Bearer <token> for JWT-based authentication.
User-Agent identifies the client software making the request. While browsers set this automatically, you might customize it for API clients or web scrapers.
Accept tells the server what content types the client can process. Setting this to application/json signals that you want JSON responses.
Custom headers should use a descriptive name without the deprecated X- prefix. Modern convention favors names like API-Key or Request-ID over X-API-Key.
CORS-related headers like Origin are set automatically by browsers during cross-origin requests to trigger CORS checks.
Here’s how to set request headers with different JavaScript libraries:
// Using fetch API
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
'Accept': 'application/json',
'API-Version': 'v2'
},
body: JSON.stringify({ name: 'John Doe', email: 'john@example.com' })
});
// Using axios
import axios from 'axios';
axios.post('https://api.example.com/users',
{ name: 'John Doe', email: 'john@example.com' },
{
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
'API-Version': 'v2'
}
}
);
Common Response Headers
Response headers provide instructions to the client about how to handle the response and enforce security policies.
Content-Type tells the client what type of data is being returned. The server should set this accurately—application/json for JSON, text/html for HTML, etc.
Content-Length specifies the size of the response body in bytes, allowing clients to track download progress.
Cache-Control dictates caching behavior. Values like no-cache, max-age=3600, or public control whether and how long responses can be cached.
Security headers are critical for protecting your application:
Strict-Transport-Securityforces HTTPS connectionsX-Frame-Optionsprevents clickjacking by controlling iframe embeddingContent-Security-Policyrestricts resource loading to prevent XSS attacksX-Content-Type-Options: nosniffprevents MIME-type sniffing
CORS headers control cross-origin access:
Access-Control-Allow-Originspecifies which origins can access the resourceAccess-Control-Allow-Methodslists permitted HTTP methodsAccess-Control-Allow-Headersdefines allowed request headers
Here’s how to set response headers in Express:
const express = require('express');
const app = express();
app.use((req, res, next) => {
// Security headers
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Content-Security-Policy', "default-src 'self'");
// CORS headers
res.setHeader('Access-Control-Allow-Origin', 'https://trusted-domain.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
app.get('/api/users', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Cache-Control', 'max-age=300'); // Cache for 5 minutes
res.json({ users: [{ id: 1, name: 'John' }] });
});
Working with Headers in JavaScript
JavaScript provides robust APIs for working with headers on both client and server.
Reading request headers in Node.js/Express is straightforward:
app.get('/api/data', (req, res) => {
const authHeader = req.headers.authorization;
const userAgent = req.headers['user-agent'];
const customHeader = req.headers['api-version'];
console.log('Authorization:', authHeader);
console.log('User Agent:', userAgent);
res.json({ received: true });
});
Accessing response headers from fetch or axios:
// With fetch
fetch('https://api.example.com/users')
.then(response => {
const contentType = response.headers.get('Content-Type');
const rateLimit = response.headers.get('X-RateLimit-Remaining');
console.log('Content-Type:', contentType);
console.log('Rate Limit Remaining:', rateLimit);
return response.json();
});
// With axios
axios.get('https://api.example.com/users')
.then(response => {
console.log('All headers:', response.headers);
console.log('Content-Type:', response.headers['content-type']);
});
The Headers API provides a clean interface for manipulating headers:
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer token123');
// Check if header exists
if (headers.has('Authorization')) {
console.log('Auth header present');
}
// Iterate over headers
for (let [key, value] of headers.entries()) {
console.log(`${key}: ${value}`);
}
fetch('https://api.example.com/users', { headers });
Practical Use Cases
Authentication with Bearer tokens is the most common use case for custom headers:
// Client-side: Login and store token
async function login(email, password) {
const response = await fetch('https://api.example.com/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const { token } = await response.json();
localStorage.setItem('authToken', token);
return token;
}
// Client-side: Use token for authenticated requests
async function fetchUserProfile() {
const token = localStorage.getItem('authToken');
const response = await fetch('https://api.example.com/profile', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
});
return response.json();
}
// Server-side: Validate token
const jwt = require('jsonwebtoken');
app.use('/api/protected', (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
});
Content negotiation allows clients to request different formats:
app.get('/api/users', (req, res) => {
const acceptHeader = req.headers.accept;
const users = [{ id: 1, name: 'John' }];
if (acceptHeader.includes('application/xml')) {
res.setHeader('Content-Type', 'application/xml');
res.send(`<users><user><id>1</id><name>John</name></user></users>`);
} else {
res.setHeader('Content-Type', 'application/json');
res.json({ users });
}
});
Custom rate limiting headers inform clients about their API usage:
app.use((req, res, next) => {
const remaining = getRateLimitRemaining(req.ip); // Your rate limit logic
res.setHeader('X-RateLimit-Limit', '100');
res.setHeader('X-RateLimit-Remaining', remaining.toString());
res.setHeader('X-RateLimit-Reset', Date.now() + 3600000);
if (remaining <= 0) {
return res.status(429).json({ error: 'Rate limit exceeded' });
}
next();
});
Best Practices and Security Considerations
Never put sensitive data in headers unless encrypted. Headers are logged by proxies, load balancers, and servers. While authorization tokens are acceptable (and expected), avoid putting passwords, credit card numbers, or other sensitive information in custom headers.
Be aware of header size limitations. Most servers limit total header size to 8KB-16KB. Browsers and proxies may have stricter limits. If you’re hitting limits, you’re probably doing something wrong—consider moving data to the request body.
Security headers checklist for production applications:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadX-Frame-Options: DENYorSAMEORIGINX-Content-Type-Options: nosniffContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'Referrer-Policy: strict-origin-when-cross-origin
Common pitfalls:
- Setting
Access-Control-Allow-Origin: *with credentials (not allowed) - Forgetting to handle preflight OPTIONS requests for CORS
- Using deprecated
X-prefix for custom headers (modern practice omits it) - Not validating header values, leading to header injection attacks
- Sending different
Content-Typethan actual content
Headers are the control plane of HTTP communication. Master them, and you’ll build more secure, efficient, and robust web applications. Set them carelessly, and you’ll create security vulnerabilities and frustrating bugs. Treat headers with the respect they deserve.