11 min read
ā¢Question 38 of 47hardNext.js Security Best Practices
Implementing security measures in Next.js.
Security Best Practices
Content Security Policy
code.txtTSX
// middleware.ts
import { NextResponse } from 'next/server';
export function middleware(request) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data:;
font-src 'self';
connect-src 'self';
frame-ancestors 'none';
`.replace(/\s{2,}/g, ' ').trim();
const response = NextResponse.next();
response.headers.set('Content-Security-Policy', cspHeader);
response.headers.set('x-nonce', nonce);
return response;
}CSRF Protection
code.txtTSX
// Server Action with CSRF check
'use server';
import { cookies, headers } from 'next/headers';
export async function updateProfile(formData: FormData) {
const origin = headers().get('origin');
const host = headers().get('host');
// Verify same origin
if (origin !== `https://${host}`) {
throw new Error('Invalid origin');
}
// Verify CSRF token
const token = formData.get('csrf');
const storedToken = cookies().get('csrf')?.value;
if (token !== storedToken) {
throw new Error('Invalid CSRF token');
}
// Proceed with update
}Input Sanitization
code.txtTSX
import DOMPurify from 'isomorphic-dompurify';
// Sanitize user HTML input
function SafeHTML({ html }: { html: string }) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
ALLOWED_ATTR: ['href'],
});
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}Security Headers
code.txtTSX
// next.config.js
const securityHeaders = [
{ key: 'X-DNS-Prefetch-Control', value: 'on' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=()' },
];
module.exports = {
async headers() {
return [{ source: '/:path*', headers: securityHeaders }];
},
};