コンテンツセキュリティポリシー (Content Security Policy)
コンテンツセキュリティポリシー (CSP) は、クロスサイトスクリプティング (XSS)、クリックジャッキング、その他のコードインジェクション攻撃などのさまざまなセキュリティ脅威からNext.jsアプリケーションを保護するために重要です。
CSPを使用することで、開発者はコンテンツソース、スクリプト、スタイルシート、画像、フォント、オブジェクト、メディア(音声、動画)、iframeなどに対して許可されるオリジンを指定できます。
例
Nonce(ノンス)
ノンスは、1回限りの使用のために作成される一意のランダムな文字列です。CSPと組み合わせて使用することで、厳格なCSPディレクティブをバイパスして特定のインラインスクリプトやスタイルの実行を選択的に許可します。
ノンスを使用する理由
CSPは悪意のあるスクリプトをブロックするように設計されていますが、インラインスクリプトが必要な正当なシナリオもあります。このような場合、ノンスは正しいノンスを持つこれらのスクリプトの実行を許可する方法を提供します。
ミドルウェアでノンスを追加
ミドルウェアを使用すると、ページがレンダリングされる前にヘッダーを追加し、ノンスを生成できます。
ページが表示されるたびに、新しいノンスを生成する必要があります。つまり、ノンスを追加するには動的レンダリングを使用する必要があります。
例:
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;
`
// 改行文字とスペースを置換
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;
`
// 改行文字とスペースを置換
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
}
デフォルトでは、ミドルウェアはすべてのリクエストで実行されます。matcher
を使用して、特定のパスでミドルウェアを実行するようにフィルタリングできます。
CSPヘッダーを必要としないプリフェッチ(next/link
からの)や静的アセットのマッチングを無視することを推奨します。
export const config = {
matcher: [
/*
* 以下で始まるリクエストパスを除くすべてのリクエストパスにマッチ:
* - api (APIルート)
* - _next/static (静的ファイル)
* - _next/image (画像最適化ファイル)
* - favicon.ico (ファビコンファイル)
*/
{
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: [
/*
* 以下で始まるリクエストパスを除くすべてのリクエストパスにマッチ:
* - api (APIルート)
* - _next/static (静的ファイル)
* - _next/image (画像最適化ファイル)
* - favicon.ico (ファビコンファイル)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
ノンスの読み取り
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}
/>
)
}
ノンスなしの場合
ノンスを必要としないアプリケーションでは、next.config.js
ファイルに直接CSPヘッダーを設定できます:
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, ''),
},
],
},
]
},
}
バージョン履歴
ノンスを適切に処理および適用するには、Next.jsのv13.4.20+
を使用することを推奨します。