Security Headers
Configure HTTP security headers
Security Headers
Protect your app with proper HTTP security headers.
Why Security Headers?
Security headers help protect against:
- Cross-Site Scripting (XSS)
- Clickjacking attacks
- MIME type sniffing
- Information leakage
- Man-in-the-middle attacks
Next.js Configuration
Configure security headers in next.config.ts:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
],
},
];
},
};
export default nextConfig;Content Security Policy
Prevent XSS attacks with CSP:
const ContentSecurityPolicy = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: https:;
font-src 'self' data:;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`;
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(),
},
],
},
];
},
};Header Explanations
Strict-Transport-Security (HSTS)
Forces browsers to use HTTPS:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadmax-age: How long to remember (2 years)includeSubDomains: Apply to all subdomainspreload: Include in browser preload list
X-Frame-Options
Prevents clickjacking:
X-Frame-Options: SAMEORIGINOptions:
DENY: Never allow framingSAMEORIGIN: Only allow same-origin framingALLOW-FROM uri: Allow specific URI
X-Content-Type-Options
Prevents MIME type sniffing:
X-Content-Type-Options: nosniffX-XSS-Protection
Legacy XSS protection:
X-XSS-Protection: 1; mode=blockReferrer-Policy
Controls referrer information:
Referrer-Policy: origin-when-cross-originOptions:
no-referrer: Never send referrerorigin: Send origin onlyorigin-when-cross-origin: Full URL for same-origin, origin for cross-originstrict-origin-when-cross-origin: Most restrictive
Permissions-Policy
Control browser features:
Permissions-Policy: camera=(), microphone=(), geolocation=()Disable unused features to reduce attack surface.
CSP Directives
default-src
Fallback for other directives:
default-src 'self';script-src
Control JavaScript sources:
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://trusted.com;Note: Avoid unsafe-inline and unsafe-eval in production.
style-src
Control CSS sources:
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;img-src
Control image sources:
img-src 'self' data: https: blob:;connect-src
Control fetch, XHR, WebSocket:
connect-src 'self' https://api.yourapp.com;font-src
Control font sources:
font-src 'self' https://fonts.gstatic.com;CSP with Nonce
For inline scripts with Next.js:
// app/[locale]/layout.tsx
import { headers } from 'next/headers';
import Script from 'next/script';
export default async function RootLayout({ children }) {
const nonce = headers().get('x-nonce');
return (
<html>
<head>
<Script
id="inline-script"
nonce={nonce}
dangerouslySetInnerHTML={{
__html: `console.log('Inline script with nonce');`,
}}
/>
</head>
<body>{children}</body>
</html>
);
}Middleware Headers
Add headers in middleware:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set('X-Custom-Header', 'value');
response.headers.set(
'Strict-Transport-Security',
'max-age=63072000; includeSubDomains; preload'
);
return response;
}Testing Headers
Using curl
curl -I https://yoursite.comUsing Security Headers Scanner
Check your headers at:
Using Browser DevTools
- Open DevTools (F12)
- Go to Network tab
- Reload page
- Click on the main document
- Check Response Headers
CORS Headers
Configure CORS for API routes:
// app/api/public/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const data = { message: 'Public API' };
return NextResponse.json(data, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
export async function OPTIONS(request: Request) {
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
},
});
}Production Headers
Complete production configuration:
const securityHeaders = [
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()',
},
{
key: 'Content-Security-Policy',
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(),
},
];
const nextConfig: NextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: securityHeaders,
},
];
},
};Best Practices
- Enable HSTS: Force HTTPS
- Use strict CSP: Prevent XSS attacks
- Disable unused features: Reduce attack surface
- Test thoroughly: Check all pages work with headers
- Monitor violations: Log CSP violations
- Regular updates: Keep headers up to date
- Use nonces: For inline scripts instead of
unsafe-inline
CSP Violation Reporting
Report CSP violations:
const ContentSecurityPolicy = `
default-src 'self';
script-src 'self';
report-uri /api/csp-report;
report-to csp-endpoint;
`;// app/api/csp-report/route.ts
export async function POST(request: Request) {
const report = await request.json();
console.log('CSP Violation:', report);
// Send to error tracking service
// Sentry.captureMessage('CSP Violation', { extra: report });
return new NextResponse(null, { status: 204 });
}Next Steps
- Implement Rate Limiting
- Add Input Validation
- Review OWASP Top 10