はじめに
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-src | JavaScriptの読み込み元を制限 |
style-src | CSSの読み込み元を制限 |
img-src | 画像の読み込み元を制限 |
font-src | フォントの読み込み元を制限 |
connect-src | Ajax、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-age | HSTSポリシーの有効期間(秒単位) |
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-downgrade | HTTPSからHTTPへの遷移時はリファラーを送信しない |
{
key: "Referrer-Policy",
// 同一オリジンではフルURL、
// 異なるオリジンへはオリジン情報のみ送信
value: "strict-origin-when-cross-origin",
}
Permissions-Policy
Permissions-Policy(旧Feature-Policy)は、ブラウザの機能やAPIへのアクセスを制御します。不要な機能を無効化することで、攻撃対象を減らせます。
制御可能な主要な機能
| 機能 | 説明 |
|---|---|
camera | カメラへのアクセス |
microphone | マイクへのアクセス |
geolocation | 位置情報へのアクセス |
payment | Payment Request APIの使用 |
usb | USB機器へのアクセス |
{
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;
セキュリティヘッダーの検証方法
設定したセキュリティヘッダーが正しく適用されているか確認する方法を紹介します。
ブラウザの開発者ツールで確認
- ブラウザでアプリケーションを開く
- 開発者ツールを開く(F12キー)
- 「Network」タブを選択
- ページをリロードしてリクエストを確認
- HTMLドキュメントのレスポンスヘッダーを確認
オンラインツールで確認
以下のオンラインツールでセキュリティヘッダーをスキャンできます。
- Security Headers - セキュリティヘッダーの包括的なスキャン
- Mozilla Observatory - Mozillaによるセキュリティ評価
まとめ
Next.jsでセキュリティヘッダーを適切に設定することで、以下の攻撃からアプリケーションを保護できます。
- XSS攻撃: CSPによるスクリプト実行の制限
- クリックジャッキング: X-Frame-Optionsによるiframe埋め込みの制限
- ダウングレード攻撃: HSTSによるHTTPS強制
- MIMEスニッフィング: X-Content-Type-Optionsによる防止
- 情報漏洩: Referrer-Policyによるリファラー制御
- 不正なAPI利用: Permissions-Policyによる機能制限
セキュリティヘッダーは「設定して終わり」ではなく、アプリケーションの要件に応じて継続的に見直すことが重要です。新しいサードパーティライブラリを導入した際や、外部サービスとの連携を追加した際には、CSPの設定を更新する必要があるかもしれません。
参考文献
- Next.js公式ドキュメント - Headers - Next.jsでのヘッダー設定方法
- MDN Web Docs - Content-Security-Policy - CSPの詳細な解説
- MDN Web Docs - Strict-Transport-Security - HSTSの解説
- OWASP Secure Headers Project - セキュリティヘッダーのベストプラクティス
- Security Headers - セキュリティヘッダーのスキャンツール