Overview
Apply security headers to protect against common web vulnerabilities. CORS, CSP, HSTS, and other headers are centralized in middleware.
What it is
HTTP response headers that protect against XSS, clickjacking, MIME sniffing, and other attacks.
Why we use it
Defense in depth - headers provide protection even if application code has vulnerabilities.
When to use
All responses. Headers are applied automatically via proxy.ts, use withSecurityHeaders() for manual application.
Key Features
- CORS with origin validation and Vary header
- Content Security Policy configuration
- HTTPS enforcement with HSTS
- Clickjacking prevention with X-Frame-Options
Quick Start
Apply Security Headers
Security headers are automatic, but can be applied manually.
// Security headers are applied automatically via proxy.ts
// For manual application in API routes:
import { withSecurityHeaders } from '@/lib/middleware/security';
export async function GET(request: Request) {
const data = await getData();
const response = NextResponse.json({ ok: true, data });
// Apply security headers to response
return withSecurityHeaders(response);
}Patterns
CORS Configuration
Handle cross-origin requests securely.
// CORS configuration for API routes
import { withCors } from '@/lib/middleware/security';
// Handle preflight requests
export async function OPTIONS(request: Request) {
return withCors(request);
}
export async function GET(request: Request) {
const data = await getData();
const response = NextResponse.json({ ok: true, data });
// Apply CORS headers based on CORS_ALLOWED_ORIGINS env var
return withCors(request, response);
}
// Environment configuration
// CORS_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.comContent Security Policy
Configure CSP directives.
// Content Security Policy configuration
// Configured in @/lib/middleware/security
const cspDirectives = {
'default-src': ["'self'"],
'script-src': ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'blob:', 'https:'],
'font-src': ["'self'"],
'connect-src': ["'self'", 'https://api.example.com'],
'frame-ancestors': ["'none'"],
'form-action': ["'self'"],
};
// Applied via middleware to all responsesSecurity Headers
Standard security headers applied.
// Security headers applied by default
const securityHeaders = {
// Prevent clickjacking
'X-Frame-Options': 'DENY',
// Prevent MIME type sniffing
'X-Content-Type-Options': 'nosniff',
// Enable XSS protection
'X-XSS-Protection': '1; mode=block',
// Enforce HTTPS
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
// Disable referrer for cross-origin requests
'Referrer-Policy': 'strict-origin-when-cross-origin',
// Permissions policy
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
};Watch Out
Overly permissive CORS (Allow-Origin: *)
Don't
// Overly permissive CORS - allows any origin
export async function OPTIONS(request: Request) {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*', // Dangerous!
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers': '*',
},
});
}Do
// Restrictive CORS with allowed origins
export async function OPTIONS(request: Request) {
const origin = request.headers.get('Origin');
const allowedOrigins = process.env.CORS_ALLOWED_ORIGINS?.split(',') ?? [];
if (!origin || !allowedOrigins.includes(origin)) {
return new Response(null, { status: 403 });
}
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Vary': 'Origin', // Important for caching
},
});
}Missing HTTPS enforcement
Don't
// Missing HTTPS enforcement
// Allows HTTP connections - vulnerable to MITM attacks
export async function GET(request: Request) {
const data = await getData();
return NextResponse.json({ ok: true, data });
// No HSTS header - browser doesn't enforce HTTPS
}Do
// HTTPS enforced with HSTS
import { withSecurityHeaders } from '@/lib/middleware/security';
export async function GET(request: Request) {
const data = await getData();
const response = NextResponse.json({ ok: true, data });
// Includes Strict-Transport-Security header
return withSecurityHeaders(response);
// Browser will enforce HTTPS for future requests
}- CSP bypasses via unsafe-inline
- Missing Vary: Origin header