@omnifolio/nextjs-security-headers
v1.0.0
Published
Production-ready Next.js middleware that adds CSP with per-request nonces, HSTS, Permissions-Policy, CORS allowlist, X-Frame-Options, and more. Drop-in security for App Router.
Maintainers
Readme
@omnifolio/nextjs-security-headers
Drop-in Next.js middleware that adds CSP with per-request nonces, HSTS, Permissions-Policy, CORS allowlist, X-Frame-Options, and more. Works with App Router and Edge Runtime.
Built by OmniFolio — Financial Intelligence Platform.
Features
- 🔒 CSP with nonces — per-request nonce for inline scripts,
strict-dynamicfor chunk loading - 🌐 CORS allowlist — strict origin matching with regex support
- 🛡️ HSTS — 1-year max-age with preload by default
- 📋 Permissions-Policy — 22 features locked down out of the box
- 🚫 X-Frame-Options — clickjacking protection
- ⚡ API route optimization — skips browser-only headers for API routes
- 🧩 Framework-agnostic core —
applySecurityHeaders()works with anyHeadersobject - 📝 Report-only mode — test CSP without breaking your site
Install
npm install @omnifolio/nextjs-security-headersQuick Start
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import {
applySecurityHeaders,
generateNonce,
isApiPath,
} from '@omnifolio/nextjs-security-headers';
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
const isApi = isApiPath(pathname);
const nonce = isApi ? '' : generateNonce();
const response = NextResponse.next({
request: { headers: new Headers(request.headers) },
});
applySecurityHeaders(response.headers, {
nonce,
isApiRoute: isApi,
isDev: process.env.NODE_ENV === 'development',
pathname,
origin: request.headers.get('origin'),
config: {
cors: {
allowedOrigins: [
'https://www.myapp.com',
'https://myapp.com',
/^https:\/\/[a-z0-9-]+\.run\.app$/, // Cloud Run previews
],
},
csp: {
connectSrc: ["'self'", 'https://api.stripe.com'],
frameSrc: ["'self'", 'https://js.stripe.com'],
},
scriptSources: [
'https://js.stripe.com',
],
},
});
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
};Using the Nonce in Server Components
// app/layout.tsx
import { headers } from 'next/headers';
export default async function RootLayout({ children }) {
const nonce = (await headers()).get('x-nonce') || '';
return (
<html>
<head>
<script nonce={nonce} src="..." />
</head>
<body>{children}</body>
</html>
);
}Configuration
CSP Directives
{
csp: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
frameSrc: ["'self'", "https://www.youtube.com"],
reportUri: "https://csp-report.example.com/report",
},
cspMode: 'enforce', // 'enforce' | 'report-only' | 'off'
scriptSources: [...], // Shorthand for additional script-src entries
}CORS
{
cors: {
allowedOrigins: [
'https://www.myapp.com',
/^https:\/\/deploy-preview-\d+\.netlify\.app$/,
],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
headers: ['Content-Type', 'Authorization', 'X-CSRF-Token'],
credentials: true,
},
}HSTS
{
hstsMaxAge: 31536000, // 1 year (default)
hstsIncludeSubDomains: true, // default
hstsPreload: true, // default
disableHSTS: false, // set true to disable
}Permissions-Policy
{
permissionsPolicy: {
camera: '(self)', // Override default '()'
geolocation: '(self)', // Already default
payment: '(self "https://js.stripe.com")',
},
}API Reference
applySecurityHeaders(headers, options)
Core function — applies all security headers to a Headers object.
buildCSP(nonce, config?)
Build a CSP string with nonce injected into script-src.
buildPermissionsPolicy(overrides?)
Build a Permissions-Policy header value.
generateNonce()
Generate a cryptographically random base64 nonce.
isApiPath(pathname, prefixes?)
Check if a path matches API route prefixes.
isOriginAllowed(origin, corsConfig)
Check if an origin is in the CORS allowlist.
Default Security Headers
| Header | Value | |--------|-------| | Content-Security-Policy | Full CSP with nonce + strict-dynamic | | X-Content-Type-Options | nosniff | | X-Frame-Options | DENY | | X-XSS-Protection | 1; mode=block | | Referrer-Policy | strict-origin-when-cross-origin | | Strict-Transport-Security | max-age=31536000; includeSubDomains; preload | | Permissions-Policy | 22 features locked down |
License
MIT — see LICENSE.
Built with ❤️ by OmniFolio
