Política de Seguridad de Contenido (CSP)
Política de Seguridad de Contenido (CSP) es importante para proteger su aplicación Next.js contra diversas amenazas de seguridad como cross-site scripting (XSS), clickjacking y otros ataques de inyección de código.
Al usar CSP, los desarrolladores pueden especificar qué orígenes son permitidos para fuentes de contenido, scripts, hojas de estilo, imágenes, fuentes, objetos, medios (audio, video), iframes y más.
Ejemplos
Nonces
Un nonce es una cadena única y aleatoria de caracteres creada para un uso único. Se utiliza junto con CSP para permitir selectivamente que ciertos scripts o estilos en línea se ejecuten, evitando las directivas estrictas de CSP.
¿Por qué usar un nonce?
Aunque los CSP están diseñados para bloquear scripts maliciosos, hay escenarios legítimos donde los scripts en línea son necesarios. En tales casos, los nonces ofrecen una forma de permitir que estos scripts se ejecuten si tienen el nonce correcto.
Agregar un nonce con Middleware
Middleware le permite agregar encabezados y generar nonces antes de que la página se renderice.
Cada vez que se visualiza una página, se debe generar un nuevo nonce. Esto significa que debe usar renderizado dinámico para agregar nonces.
Por ejemplo:
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
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';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
// Reemplazar saltos de línea y espacios
const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, ' ')
.trim()
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-nonce', nonce)
requestHeaders.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue
)
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
})
response.headers.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue
)
return response
}
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';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
// Reemplazar saltos de línea y espacios
const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, ' ')
.trim()
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-nonce', nonce)
requestHeaders.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue
)
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
})
response.headers.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue
)
return response
}
Por defecto, Middleware se ejecuta en todas las solicitudes. Puede filtrar Middleware para que se ejecute en rutas específicas usando un matcher
.
Recomendamos ignorar las prefetch (de next/link
) y los recursos estáticos que no necesitan el encabezado CSP.
export const config = {
matcher: [
/*
* Coincide con todas las rutas excepto las que comienzan con:
* - api (rutas API)
* - _next/static (archivos estáticos)
* - _next/image (archivos de optimización de imágenes)
* - favicon.ico (archivo favicon)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
export const config = {
matcher: [
/*
* Coincide con todas las rutas excepto las que comienzan con:
* - api (rutas API)
* - _next/static (archivos estáticos)
* - _next/image (archivos de optimización de imágenes)
* - favicon.ico (archivo favicon)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
Leyendo el nonce
Ahora puede leer el nonce desde un Componente de Servidor usando headers
:
import { headers } from 'next/headers'
import Script from 'next/script'
export default function Page() {
const nonce = headers().get('x-nonce')
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
)
}
import { headers } from 'next/headers'
import Script from 'next/script'
export default function Page() {
const nonce = headers().get('x-nonce')
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
)
}
Sin Nonces
Para aplicaciones que no requieren nonces, puede configurar el encabezado CSP directamente en su archivo next.config.js
:
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\n/g, ''),
},
],
},
]
},
}
Historial de versiones
Recomendamos usar v13.4.20+
de Next.js para manejar y aplicar nonces correctamente.