Documentation Next.js

はじめに

Webアプリケーションを開発する際、機能の実装だけでなくセキュリティ対策も重要な要素です。特にNext.jsでアプリケーションを構築する場合、セキュリティヘッダーを適切に設定することで、さまざまな攻撃からアプリケーションとユーザーを守れます。

この記事では、Next.jsにおけるセキュリティヘッダーの設定方法を解説します。具体的には以下の内容を扱います。

  • セキュリティヘッダーの基本概念と重要性
  • 主要なセキュリティヘッダーの種類と役割
  • Next.jsでの具体的な設定方法
  • 本番環境向けの包括的な設定例

セキュリティヘッダーとは

セキュリティヘッダーは、Webサーバーからブラウザに送信されるHTTPレスポンスヘッダーの一種です。これらのヘッダーは、ブラウザに対して「このページをどのように扱うべきか」という指示を与え、さまざまなセキュリティ上の脅威からユーザーを保護します。

なぜセキュリティヘッダーが重要なのか

セキュリティヘッダーを設定しないと、以下のような攻撃に対して脆弱になります。

攻撃の種類説明対策ヘッダー
XSS(クロスサイトスクリプティング)悪意のあるスクリプトがページに注入されるContent-Security-Policy
クリックジャッキング透明なiframeを重ねてユーザーを騙すX-Frame-Options
MITMダウングレード攻撃HTTPS接続をHTTPに強制的にダウングレードStrict-Transport-Security
MIMEタイプスニッフィングファイルタイプを偽装して悪意のあるコードを実行X-Content-Type-Options

主要なセキュリティヘッダー

Content Security Policy(CSP)

CSP(Content Security Policy)は、最も重要なセキュリティヘッダーの1つです。このヘッダーは、ページ内で実行できるスクリプト、読み込めるスタイルシート、画像などのリソースの出所(オリジン)を制限します。

CSPの主要なディレクティブ

ディレクティブ説明
default-srcすべてのリソースタイプのデフォルトポリシー
script-srcJavaScriptの読み込み元を制限
style-srcCSSの読み込み元を制限
img-src画像の読み込み元を制限
font-srcフォントの読み込み元を制限
connect-srcAjax、WebSocket等の接続先を制限
frame-ancestorsこのページをiframeで埋め込める親ページを制限

Next.jsでのCSP設定例

next.config.jsファイルでCSPを設定します。

/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        // すべてのルートに適用
        source: "/(.*)",
        headers: [
          {
            key: "Content-Security-Policy",
            value: [
              // デフォルトは自身のドメインのみ許可
              "default-src 'self'",
              // スクリプトは自身と信頼できるCDNから許可
              "script-src 'self' https://cdn.example.com",
              // スタイルは自身とインラインスタイルを許可
              "style-src 'self' 'unsafe-inline'",
              // 画像は自身とdata URIを許可
              "img-src 'self' data: https://images.example.com",
              // フォントは自身とGoogle Fontsから許可
              "font-src 'self' https://fonts.gstatic.com",
              // APIへの接続は自身のドメインのみ
              "connect-src 'self' https://api.example.com",
              // iframeでの埋め込みは同一オリジンのみ
              "frame-ancestors 'self'",
            ].join("; "),
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

開発環境と本番環境でCSPを分ける

開発時はホットリロードなどの機能を使用するため、より緩いCSPが必要になることがあります。

/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    // 開発環境かどうかを判定
    const isDevelopment = process.env.NODE_ENV === "development";

    // 開発環境用のCSP(より緩い設定)
    const devCSP = [
      "default-src 'self'",
      // 開発時はevalとインラインスクリプトを許可
      "script-src 'self' 'unsafe-eval' 'unsafe-inline'",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: blob:",
      "connect-src 'self' ws: wss:",
    ].join("; ");

    // 本番環境用のCSP(厳格な設定)
    const prodCSP = [
      "default-src 'self'",
      "script-src 'self'",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data:",
      "connect-src 'self'",
      "frame-ancestors 'none'",
      // XSS検出時にページの読み込みをブロック
      "block-all-mixed-content",
    ].join("; ");

    return [
      {
        source: "/(.*)",
        headers: [
          {
            key: "Content-Security-Policy",
            value: isDevelopment ? devCSP : prodCSP,
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

X-Frame-Options

X-Frame-Optionsは、クリックジャッキング攻撃を防ぐためのヘッダーです。クリックジャッキングとは、攻撃者が透明なiframeを使用して、ユーザーに意図しない操作をさせる攻撃手法です。

設定可能な値

説明
DENYすべてのiframe埋め込みを禁止
SAMEORIGIN同一オリジンからの埋め込みのみ許可
{
  key: "X-Frame-Options",
  // すべてのiframe埋め込みを禁止
  value: "DENY",
}

同一オリジンからの埋め込みを許可する場合は以下のように設定します。

{
  key: "X-Frame-Options",
  // 同じドメインからのiframe埋め込みは許可
  value: "SAMEORIGIN",
}

Strict-Transport-Security(HSTS)

HSTS(HTTP Strict Transport Security)は、ブラウザに対して常にHTTPS接続を使用するよう強制するヘッダーです。これにより、HTTPへのダウングレード攻撃を防止できます。

HSTSの主要なオプション

オプション説明
max-ageHSTSポリシーの有効期間(秒単位)
includeSubDomainsサブドメインにもポリシーを適用
preloadブラウザのHSTSプリロードリストへの登録を希望
{
  key: "Strict-Transport-Security",
  // 1年間(31536000秒)HTTPS接続を強制
  // サブドメインにも適用し、プリロードリストへの登録を希望
  value: "max-age=31536000; includeSubDomains; preload",
}

preloadオプションを使用する場合は、HSTS Preload Listに登録申請が必要です。一度登録すると解除が困難なため、慎重に検討してください。

X-Content-Type-Options

X-Content-Type-Optionsは、ブラウザのMIMEタイプスニッフィングを防止します。MIMEタイプスニッフィングとは、ブラウザがContent-Typeヘッダーを無視してファイルの内容からタイプを推測する動作で、これが悪用されると意図しないスクリプトが実行される可能性があります。

{
  key: "X-Content-Type-Options",
  // MIMEタイプスニッフィングを無効化
  value: "nosniff",
}

Referrer-Policy

Referrer-Policyは、ページ遷移時に送信されるリファラー情報の範囲を制御します。リファラー情報の漏洩はプライバシーやセキュリティ上の問題となる場合があります。

主要なポリシー値

説明
no-referrerリファラーを一切送信しない
same-origin同一オリジンへの遷移時のみリファラーを送信
strict-origin-when-cross-origin同一オリジンではフルURL、クロスオリジンではオリジンのみ送信
no-referrer-when-downgradeHTTPSからHTTPへの遷移時はリファラーを送信しない
{
  key: "Referrer-Policy",
  // 同一オリジンではフルURL、
  // 異なるオリジンへはオリジン情報のみ送信
  value: "strict-origin-when-cross-origin",
}

Permissions-Policy

Permissions-Policy(旧Feature-Policy)は、ブラウザの機能やAPIへのアクセスを制御します。不要な機能を無効化することで、攻撃対象を減らせます。

制御可能な主要な機能

機能説明
cameraカメラへのアクセス
microphoneマイクへのアクセス
geolocation位置情報へのアクセス
paymentPayment Request APIの使用
usbUSB機器へのアクセス
{
  key: "Permissions-Policy",
  // カメラ、マイク、位置情報、決済APIを無効化
  value: [
    "camera=()",
    "microphone=()",
    "geolocation=()",
    "payment=()",
  ].join(", "),
}

特定のオリジンに機能を許可する場合は以下のように設定します。

{
  key: "Permissions-Policy",
  // 位置情報は自身のドメインのみ許可
  value: "geolocation=(self), camera=(), microphone=()",
}

包括的な設定例

本番環境向けの包括的なセキュリティヘッダー設定の例を紹介します。

/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        // すべてのルートに適用
        source: "/(.*)",
        headers: [
          // CSP: リソースの読み込み元を制限
          {
            key: "Content-Security-Policy",
            value: [
              "default-src 'self'",
              "script-src 'self'",
              "style-src 'self' 'unsafe-inline'",
              "img-src 'self' data: https:",
              "font-src 'self'",
              "connect-src 'self'",
              "frame-ancestors 'none'",
              "base-uri 'self'",
              "form-action 'self'",
            ].join("; "),
          },
          // クリックジャッキング対策
          {
            key: "X-Frame-Options",
            value: "DENY",
          },
          // HTTPS強制(1年間有効)
          {
            key: "Strict-Transport-Security",
            value: "max-age=31536000; includeSubDomains; preload",
          },
          // MIMEタイプスニッフィング防止
          {
            key: "X-Content-Type-Options",
            value: "nosniff",
          },
          // リファラー情報の制御
          {
            key: "Referrer-Policy",
            value: "strict-origin-when-cross-origin",
          },
          // ブラウザ機能の制限
          {
            key: "Permissions-Policy",
            value: "camera=(), microphone=(), geolocation=()",
          },
          // XSS保護(レガシーブラウザ向け)
          {
            key: "X-XSS-Protection",
            value: "1; mode=block",
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

セキュリティヘッダーの検証方法

設定したセキュリティヘッダーが正しく適用されているか確認する方法を紹介します。

ブラウザの開発者ツールで確認

  1. ブラウザでアプリケーションを開く
  2. 開発者ツールを開く(F12キー)
  3. 「Network」タブを選択
  4. ページをリロードしてリクエストを確認
  5. HTMLドキュメントのレスポンスヘッダーを確認

オンラインツールで確認

以下のオンラインツールでセキュリティヘッダーをスキャンできます。

まとめ

Next.jsでセキュリティヘッダーを適切に設定することで、以下の攻撃からアプリケーションを保護できます。

  • XSS攻撃: CSPによるスクリプト実行の制限
  • クリックジャッキング: X-Frame-Optionsによるiframe埋め込みの制限
  • ダウングレード攻撃: HSTSによるHTTPS強制
  • MIMEスニッフィング: X-Content-Type-Optionsによる防止
  • 情報漏洩: Referrer-Policyによるリファラー制御
  • 不正なAPI利用: Permissions-Policyによる機能制限

セキュリティヘッダーは「設定して終わり」ではなく、アプリケーションの要件に応じて継続的に見直すことが重要です。新しいサードパーティライブラリを導入した際や、外部サービスとの連携を追加した際には、CSPの設定を更新する必要があるかもしれません。

参考文献

円