Security Headers
Patterns for HTTP security headers in Next.js applications.
Overview#
Security headers protect your application from common attacks like XSS, clickjacking, and MIME sniffing. This pattern covers:
- Next.js headers configuration
- Content Security Policy (CSP)
- CORS configuration
- Cache control for sensitive data
Prerequisites#
# No additional packages requiredCode Example#
Next.js Headers Configuration#
1// next.config.js
2/** @type {import('next').NextConfig} */
3const nextConfig = {
4 async headers() {
5 return [
6 {
7 source: '/:path*',
8 headers: [
9 {
10 key: 'X-DNS-Prefetch-Control',
11 value: 'on'
12 },
13 {
14 key: 'Strict-Transport-Security',
15 value: 'max-age=63072000; includeSubDomains; preload'
16 },
17 {
18 key: 'X-Frame-Options',
19 value: 'SAMEORIGIN'
20 },
21 {
22 key: 'X-Content-Type-Options',
23 value: 'nosniff'
24 },
25 {
26 key: 'Referrer-Policy',
27 value: 'strict-origin-when-cross-origin'
28 },
29 {
30 key: 'Permissions-Policy',
31 value: 'camera=(), microphone=(), geolocation=()'
32 }
33 ]
34 }
35 ]
36 }
37}
38
39module.exports = nextConfigContent Security Policy Builder#
1// lib/csp.ts
2type CspDirective =
3 | 'default-src'
4 | 'script-src'
5 | 'style-src'
6 | 'img-src'
7 | 'font-src'
8 | 'connect-src'
9 | 'media-src'
10 | 'object-src'
11 | 'frame-src'
12 | 'frame-ancestors'
13 | 'base-uri'
14 | 'form-action'
15 | 'upgrade-insecure-requests'
16
17export function buildCsp(directives: Partial<Record<CspDirective, string[]>>): string {
18 const defaults: Partial<Record<CspDirective, string[]>> = {
19 'default-src': ["'self'"],
20 'script-src': ["'self'"],
21 'style-src': ["'self'", "'unsafe-inline'"],
22 'img-src': ["'self'", 'data:', 'https:'],
23 'font-src': ["'self'"],
24 'connect-src': ["'self'"],
25 'frame-ancestors': ["'none'"],
26 'base-uri': ["'self'"],
27 'form-action': ["'self'"]
28 }
29
30 const merged = { ...defaults, ...directives }
31
32 return Object.entries(merged)
33 .map(([key, values]) => `${key} ${values!.join(' ')}`)
34 .join('; ')
35}
36
37// Usage with Stripe
38const csp = buildCsp({
39 'script-src': ["'self'", 'https://js.stripe.com'],
40 'frame-src': ["'self'", 'https://js.stripe.com'],
41 'connect-src': ["'self'", 'https://api.stripe.com']
42})Middleware with Security Headers#
1// middleware.ts
2import { NextResponse } from 'next/server'
3import type { NextRequest } from 'next/server'
4import { buildCsp } from '@/lib/csp'
5
6export function middleware(request: NextRequest) {
7 const response = NextResponse.next()
8
9 // Generate nonce for inline scripts
10 const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
11
12 const csp = buildCsp({
13 'script-src': ["'self'", `'nonce-${nonce}'`],
14 'style-src': ["'self'", "'unsafe-inline'"],
15 'connect-src': [
16 "'self'",
17 'https://api.stripe.com',
18 process.env.NEXT_PUBLIC_API_URL!
19 ]
20 })
21
22 response.headers.set('Content-Security-Policy', csp)
23 response.headers.set('X-Nonce', nonce)
24
25 return response
26}Using Nonce for Inline Scripts#
1// app/layout.tsx
2import { headers } from 'next/headers'
3
4export default async function RootLayout({ children }: { children: React.ReactNode }) {
5 const headersList = await headers()
6 const nonce = headersList.get('X-Nonce') ?? ''
7
8 return (
9 <html lang="en">
10 <head>
11 <script
12 nonce={nonce}
13 dangerouslySetInnerHTML={{
14 __html: `
15 window.__CONFIG__ = ${JSON.stringify({
16 apiUrl: process.env.NEXT_PUBLIC_API_URL
17 })}
18 `
19 }}
20 />
21 </head>
22 <body>{children}</body>
23 </html>
24 )
25}CORS Headers#
1// lib/cors.ts
2const ALLOWED_ORIGINS = [
3 'https://app.example.com',
4 'https://admin.example.com'
5]
6
7export function getCorsHeaders(origin: string | null) {
8 const headers: Record<string, string> = {}
9
10 if (origin && ALLOWED_ORIGINS.includes(origin)) {
11 headers['Access-Control-Allow-Origin'] = origin
12 headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
13 headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
14 headers['Access-Control-Allow-Credentials'] = 'true'
15 headers['Access-Control-Max-Age'] = '86400'
16 }
17
18 return headers
19}
20
21// Usage in API route
22export async function OPTIONS(request: Request) {
23 const origin = request.headers.get('origin')
24 const corsHeaders = getCorsHeaders(origin)
25
26 return new Response(null, {
27 status: 204,
28 headers: corsHeaders
29 })
30}Cache Control for Sensitive Data#
1// API responses with sensitive data
2export async function GET() {
3 const sensitiveData = await getSensitiveData()
4
5 return Response.json(sensitiveData, {
6 headers: {
7 'Cache-Control': 'no-store, no-cache, must-revalidate, private',
8 'Pragma': 'no-cache',
9 'Expires': '0'
10 }
11 })
12}
13
14// Static/public data
15export async function GET() {
16 const publicData = await getPublicData()
17
18 return Response.json(publicData, {
19 headers: {
20 'Cache-Control': 'public, max-age=3600, s-maxage=3600',
21 'CDN-Cache-Control': 'public, max-age=86400'
22 }
23 })
24}Permissions Policy#
1// Restrict browser features
2const permissionsPolicy = [
3 'camera=()',
4 'microphone=()',
5 'geolocation=()',
6 'interest-cohort=()', // Disable FLoC
7 'payment=(self)',
8 'usb=()',
9 'magnetometer=()',
10 'gyroscope=()',
11 'accelerometer=()'
12].join(', ')
13
14response.headers.set('Permissions-Policy', permissionsPolicy)Usage Instructions#
- Add security headers in
next.config.jsfor static configuration - Use middleware for dynamic headers like CSP with nonces
- Configure CORS for API endpoints that need cross-origin access
- Set appropriate cache control for sensitive vs public data
- Use Permissions-Policy to disable unused browser features
Best Practices#
- Start strict - Begin with restrictive policies and loosen as needed
- Use nonces - Avoid
unsafe-inlinefor scripts when possible - Test thoroughly - Security headers can break functionality
- Monitor CSP violations - Use
report-urito track issues - HSTS preload - Submit your domain to the HSTS preload list
- Review regularly - Update policies as your app evolves
Related Patterns#
- CSRF Protection - Cross-site request forgery prevention
- Input Validation - Validate all user input
- API Middleware - Request middleware patterns
- Rate Limiting - Protect against abuse