Loading...
Loading...
> Configure HTTP security headers including HSTS, CSP, X-Frame-Options, and more.
Security headers protect your application from common web vulnerabilities like XSS, clickjacking, and man-in-the-middle attacks. Fabrk includes production-ready defaults you can customize.
Add security headers in next.config.js
1// next.config.js23/** @type {import('next').NextConfig} */4const nextConfig = {5 async headers() {6 return [7 {8 source: "/:path*",9 headers: [10 {11 key: "X-DNS-Prefetch-Control",12 value: "on",13 },14 {15 key: "Strict-Transport-Security",16 value: "max-age=63072000; includeSubDomains; preload",17 },18 {19 key: "X-Frame-Options",20 value: "SAMEORIGIN",21 },22 {23 key: "X-Content-Type-Options",24 value: "nosniff",25 },26 {27 key: "Referrer-Policy",28 value: "strict-origin-when-cross-origin",29 },30 {31 key: "Permissions-Policy",32 value: "camera=(), microphone=(), geolocation=()",33 },34 ],35 },36 ];37 },38};3940module.exports = nextConfig;
Configure a strict CSP to prevent XSS attacks
1// next.config.js23const ContentSecurityPolicy = `4 default-src 'self';5 script-src 'self' 'unsafe-eval' 'unsafe-inline' https://js.stripe.com;6 style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;7 font-src 'self' https://fonts.gstatic.com;8 img-src 'self' data: https: blob:;9 connect-src 'self' https://api.stripe.com https://vitals.vercel-insights.com;10 frame-src 'self' https://js.stripe.com https://hooks.stripe.com;11 object-src 'none';12 base-uri 'self';13 form-action 'self';14 frame-ancestors 'none';15 upgrade-insecure-requests;16`;1718const nextConfig = {19 async headers() {20 return [21 {22 source: "/:path*",23 headers: [24 {25 key: "Content-Security-Policy",26 value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),27 },28 // ... other headers29 ],30 },31 ];32 },33};
Use nonces for stricter CSP without unsafe-inline
1// src/middleware.ts23import { NextRequest, NextResponse } from "next/server";45export function middleware(request: NextRequest) {6 // Generate nonce7 const nonce = Buffer.from(crypto.randomUUID()).toString("base64");89 // Create CSP with nonce10 const cspHeader = `11 default-src 'self';12 script-src 'self' 'nonce-${nonce}' 'strict-dynamic';13 style-src 'self' 'nonce-${nonce}';14 img-src 'self' data: https:;15 font-src 'self';16 connect-src 'self';17 frame-ancestors 'none';18 base-uri 'self';19 form-action 'self';20 `;2122 // Set headers23 const response = NextResponse.next();24 response.headers.set(25 "Content-Security-Policy",26 cspHeader.replace(/\s{2,}/g, " ").trim()27 );28 response.headers.set("x-nonce", nonce);2930 return response;31}3233// Access nonce in pages34// src/app/layout.tsx3536import { headers } from "next/headers";3738export default async function RootLayout({39 children,40}: {41 children: React.ReactNode;42}) {43 const headersList = await headers();44 const nonce = headersList.get("x-nonce") || "";4546 return (47 <html lang="en">48 <body>49 <script nonce={nonce}>50 {/* Inline scripts with nonce */}51 </script>52 {children}53 </body>54 </html>55 );56}
Enforce HTTPS with Strict-Transport-Security
1$// HSTS Header Options23$// Standard (2 years)4$"max-age=63072000; includeSubDomains"56$// With preload (submit to hstspreload.org)7$"max-age=63072000; includeSubDomains; preload"89$// During testing (5 minutes)10$"max-age=300"1112$// Parameters:13$// - max-age: Time in seconds browser remembers HTTPS14$// - includeSubDomains: Apply to all subdomains15$// - preload: Allow browser vendors to hardcode
Control browser feature access
1// Common Permissions Policy configurations23// Minimal (most secure)4{5 key: "Permissions-Policy",6 value: "camera=(), microphone=(), geolocation=(), browsing-topics=()"7}89// Allow specific features for same origin10{11 key: "Permissions-Policy",12 value: "camera=(self), microphone=(self), geolocation=(self)"13}1415// Allow for specific domains16{17 key: "Permissions-Policy",18 value: "camera=(self 'https://trusted.example.com')"19}2021// Full list of directives:22// accelerometer, autoplay, camera, cross-origin-isolated,23// display-capture, encrypted-media, fullscreen, geolocation,24// gyroscope, keyboard-map, magnetometer, microphone, midi,25// payment, picture-in-picture, publickey-credentials-get,26// screen-wake-lock, sync-xhr, usb, web-share, xr-spatial-tracking
Alternative: Configure headers in vercel.json
1// vercel.json23{4 "headers": [5 {6 "source": "/(.*)",7 "headers": [8 {9 "key": "X-Frame-Options",10 "value": "DENY"11 },12 {13 "key": "X-Content-Type-Options",14 "value": "nosniff"15 },16 {17 "key": "Referrer-Policy",18 "value": "strict-origin-when-cross-origin"19 },20 {21 "key": "Strict-Transport-Security",22 "value": "max-age=63072000; includeSubDomains; preload"23 }24 ]25 }26 ]27}
Verify your security headers are working
1$# Test with curl2$curl -I https://yoursite.com34$# Online tools5$# - https://securityheaders.com6$# - https://observatory.mozilla.org7$# - https://csp-evaluator.withgoogle.com89$# Browser DevTools10$# Network tab → Select request → Headers tab
Only use the HSTS preload directive once you're certain all subdomains support HTTPS. It's difficult to remove from the preload list once submitted.