Politique de sécurité du contenu (Content Security Policy)
La Politique de sécurité du contenu (CSP) est importante pour protéger votre application Next.js contre diverses menaces de sécurité telles que le cross-site scripting (XSS), le clickjacking et d'autres attaques par injection de code.
En utilisant CSP, les développeurs peuvent spécifier quelles origines sont autorisées pour les sources de contenu, scripts, feuilles de style, images, polices, objets, médias (audio, vidéo), iframes et plus encore.
Exemples
Nonces
Un nonce est une chaîne de caractères unique et aléatoire créée pour une utilisation unique. Il est utilisé conjointement avec CSP pour autoriser sélectivement certains scripts ou styles en ligne à s'exécuter, contournant ainsi les directives strictes de CSP.
Pourquoi utiliser un nonce ?
Bien que les CSP soient conçus pour bloquer les scripts malveillants, il existe des scénarios légitimes où des scripts en ligne sont nécessaires. Dans ces cas, les nonces offrent un moyen d'autoriser ces scripts à s'exécuter s'ils possèdent le nonce correct.
Ajout d'un nonce avec Middleware
Le Middleware vous permet d'ajouter des en-têtes et de générer des nonces avant le rendu de la page.
À chaque affichage d'une page, un nouveau nonce doit être généré. Cela signifie que vous devez utiliser le rendu dynamique pour ajouter des nonces.
Par exemple :
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;
`
// Remplacer les sauts de ligne et les espaces
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;
`
// Remplacer les sauts de ligne et les espaces
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
}
Par défaut, le Middleware s'exécute sur toutes les requêtes. Vous pouvez filtrer le Middleware pour qu'il s'exécute sur des chemins spécifiques en utilisant un matcher
.
Nous recommandons d'ignorer les préchargements (depuis next/link
) et les ressources statiques qui n'ont pas besoin de l'en-tête CSP.
export const config = {
matcher: [
/*
* Correspond à tous les chemins de requête sauf ceux commençant par :
* - api (routes API)
* - _next/static (fichiers statiques)
* - _next/image (fichiers d'optimisation d'image)
* - favicon.ico (fichier 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: [
/*
* Correspond à tous les chemins de requête sauf ceux commençant par :
* - api (routes API)
* - _next/static (fichiers statiques)
* - _next/image (fichiers d'optimisation d'image)
* - favicon.ico (fichier favicon)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
Lecture du nonce
Vous pouvez maintenant lire le nonce depuis un Composant Serveur en utilisant 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}
/>
)
}
Sans Nonces
Pour les applications qui ne nécessitent pas de nonces, vous pouvez définir l'en-tête CSP directement dans votre fichier 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, ''),
},
],
},
]
},
}
Historique des versions
Nous recommandons d'utiliser Next.js v13.4.20+
pour gérer et appliquer correctement les nonces.