セキュリティヘッダー実装ガイド

中級 | 25分 で読める | 2025.01.10

公式ドキュメント

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

セキュリティヘッダーは、ブラウザにセキュリティポリシーを伝えるHTTPレスポンスヘッダーです。XSS、クリックジャッキング、MIMEスニッフィングなどの攻撃を防ぎます。

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

Content-Security-Policy (CSP)

CSPはXSSやデータインジェクション攻撃を防止する最も重要なヘッダーです。

Content-Security-Policy: default-src 'self';
 script-src 'self' 'unsafe-inline' https://cdn.example.com;
 style-src 'self' 'unsafe-inline';
 img-src 'self' data: https:;
 font-src 'self' https://fonts.gstatic.com;
 connect-src 'self' https://api.example.com;
 frame-ancestors 'none';

Strict-Transport-Security (HSTS)

HTTPSの使用を強制します。

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

X-Content-Type-Options

MIMEタイプスニッフィングを防止します。

X-Content-Type-Options: nosniff

X-Frame-Options

クリックジャッキングを防止します。

X-Frame-Options: DENY
# または
X-Frame-Options: SAMEORIGIN

Permissions-Policy

ブラウザ機能へのアクセスを制御します。

Permissions-Policy: camera=(), microphone=(), geolocation=(self)

Referrer-Policy

リファラー情報の送信を制御します。

Referrer-Policy: strict-origin-when-cross-origin

Next.js での実装

next.config.js

// next.config.js
const securityHeaders = [
 {
 key: 'X-DNS-Prefetch-Control',
 value: 'on'
 },
 {
 key: 'Strict-Transport-Security',
 value: 'max-age=63072000; includeSubDomains; preload'
 },
 {
 key: 'X-Content-Type-Options',
 value: 'nosniff'
 },
 {
 key: 'X-Frame-Options',
 value: 'DENY'
 },
 {
 key: 'X-XSS-Protection',
 value: '1; mode=block'
 },
 {
 key: 'Referrer-Policy',
 value: 'strict-origin-when-cross-origin'
 },
 {
 key: 'Permissions-Policy',
 value: 'camera=(), microphone=(), geolocation=()'
 },
 {
 key: 'Content-Security-Policy',
 value: `
 default-src 'self';
 script-src 'self' 'unsafe-eval' 'unsafe-inline';
 style-src 'self' 'unsafe-inline';
 img-src 'self' data: https:;
 font-src 'self';
 connect-src 'self' https://api.example.com;
 frame-ancestors 'none';
 `.replace(/\s{2,}/g, ' ').trim()
 }
];

module.exports = {
 async headers() {
 return [
 {
 source: '/:path*',
 headers: securityHeaders,
 },
 ];
 },
};

Express での実装

実践メモ: ExpressではHelmetライブラリを使うと、主要なセキュリティヘッダーをワンライナーで設定できます。個別にカスタマイズも可能です。

Helmet ライブラリ

// app.ts
import express from 'express';
import helmet from 'helmet';

const app = express();

// 基本的なセキュリティヘッダー
app.use(helmet());

// カスタム設定
app.use(
 helmet({
 contentSecurityPolicy: {
 directives: {
 defaultSrc: ["'self'"],
 scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.example.com"],
 styleSrc: ["'self'", "'unsafe-inline'"],
 imgSrc: ["'self'", "data:", "https:"],
 connectSrc: ["'self'", "https://api.example.com"],
 fontSrc: ["'self'", "https://fonts.gstatic.com"],
 frameAncestors: ["'none'"],
 },
 },
 hsts: {
 maxAge: 63072000,
 includeSubDomains: true,
 preload: true,
 },
 })
);

手動設定

app.use((req, res, next) => {
 res.setHeader('X-Content-Type-Options', 'nosniff');
 res.setHeader('X-Frame-Options', 'DENY');
 res.setHeader('X-XSS-Protection', '1; mode=block');
 res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
 res.setHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload');
 next();
});

Nginx での設定

# /etc/nginx/conf.d/security-headers.conf
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';" always;

Vercel での設定

// vercel.json
{
 "headers": [
 {
 "source": "/(.*)",
 "headers": [
 { "key": "X-Content-Type-Options", "value": "nosniff" },
 { "key": "X-Frame-Options", "value": "DENY" },
 { "key": "X-XSS-Protection", "value": "1; mode=block" },
 { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
 { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
 ]
 }
 ]
}

CORS設定

// Express CORS
import cors from 'cors';

app.use(cors({
 origin: ['https://example.com', 'https://app.example.com'],
 methods: ['GET', 'POST', 'PUT', 'DELETE'],
 allowedHeaders: ['Content-Type', 'Authorization'],
 credentials: true,
 maxAge: 86400, // 24時間
}));
# Nginx CORS
location /api {
 if ($request_method = 'OPTIONS') {
 add_header 'Access-Control-Allow-Origin' 'https://example.com';
 add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
 add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
 add_header 'Access-Control-Max-Age' 86400;
 return 204;
 }

 add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
}

セキュリティチェック

オンラインツール

# SecurityHeaders.com
# https://securityheaders.com/?q=example.com

# Mozilla Observatory
# https://observatory.mozilla.org/

curlでの確認

curl -I https://example.com

# 特定ヘッダーの確認
curl -I https://example.com | grep -i "content-security-policy"
curl -I https://example.com | grep -i "strict-transport"

CSP違反のレポート

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
// CSP違反レポートの受信
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
 console.log('CSP Violation:', req.body['csp-report']);
 res.status(204).end();
});

ベストプラクティス

ポイント: CSPはReport-Onlyモードで段階的に導入しましょう。いきなり本番適用すると、正当なスクリプトがブロックされてサイトが動作しなくなるリスクがあります

1. 段階的な導入

# まずReport-Onlyで影響を確認
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

# 問題なければ本番適用
Content-Security-Policy: default-src 'self'

2. 環境ごとの設定

const isDev = process.env.NODE_ENV === 'development';

const cspDirectives = {
 defaultSrc: ["'self'"],
 scriptSrc: isDev
 ? ["'self'", "'unsafe-eval'", "'unsafe-inline'"]
 : ["'self'"],
 // ...
};

関連記事

まとめ

セキュリティヘッダーは、Webアプリケーションの防御層として重要です。CSPは段階的に導入し、定期的にセキュリティスキャンを実施しましょう。

参考リソース

← 一覧に戻る
PR
PR
PR
PR