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

中級 | 25分 で読める | 2025.01.10

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

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

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

Content-Security-Policy (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 での実装

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();
});

ベストプラクティス

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は段階的に導入し、定期的にセキュリティスキャンを実施しましょう。

← 一覧に戻る