Menu

コンテンツセキュリティポリシー

コンテンツセキュリティポリシー(CSP)は、クロスサイトスクリプティング(XSS)、クリックジャッキング、その他のコード注入攻撃など、様々なセキュリティ脅威からNext.jsアプリケーションを守る上で重要です。

CSPを使用することで、開発者はコンテンツソース、スクリプト、スタイルシート、画像、フォント、オブジェクト、メディア(オーディオ、ビデオ)、iframeなどの許可可能な送信元を指定できます。

ノンス

ノンスは、1回限りの使用のために作成された、一意のランダムな文字列です。厳格なCSPディレクティブをバイパスして、特定のインラインスクリプトやスタイルの実行を選択的に許可するために、CSPと組み合わせて使用されます。

ノンスを使用する理由

CSPは悪意のあるスクリプトをブロックするように設計されていますが、インラインスクリプトが必要な正当なシナリオがあります。そのような場合、ノンスは正しいノンスを持つスクリプトの実行を許可する方法を提供します。

ミドルウェアでのノンスの追加

ミドルウェアにより、ページがレンダリングされる前にヘッダーを追加し、ノンスを生成できます。

ページが表示されるたびに、新しいノンスを生成する必要があります。つまり、ノンスを追加するには動的レンダリングを使用する必要があります

例:

middleware.ts
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
}
middleware.js
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を使用して、ミドルウェアを特定のパスに対してのみ実行するようにフィルタリングできます。

next/linkからのプリフェッチとCSPヘッダーを必要としない静的アセットを無視することをお勧めします。

middleware.ts
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' },
      ],
    },
  ],
}
middleware.js
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を使用してノンスを読み取ることができます:

app/page.tsx
TypeScript
import { headers } from 'next/headers'
import Script from 'next/script'
 
export default async function Page() {
  const nonce = (await headers()).get('x-nonce')
 
  return (
    <Script
      src="https://www.googletagmanager.com/gtag/js"
      strategy="afterInteractive"
      nonce={nonce}
    />
  )
}

ノンスなし

ノンスを必要としないアプリケーションの場合、next.config.jsファイルでCSPヘッダーを直接設定できます:

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, ''),
          },
        ],
      },
    ]
  },
}

バージョン履歴

ノンスを適切に処理および適用するには、Next.jsのv13.4.20+を使用することをお勧めします。