Access-Control-Allow-Headers を徹底解説:CORS の仕組み・設定・注意点と実践例

はじめに

Access-Control-Allow-Headers は、ブラウザの同一生成元ポリシー(Same-Origin Policy)を回避して安全にクロスオリジン通信を行うための CORS(Cross-Origin Resource Sharing)の重要なレスポンスヘッダーの一つです。本コラムでは、仕組み、プリフライト、具体的な設定例(Express、Nginx、Apache)、よくあるトラブルシューティング、セキュリティ上の注意点、デバッグ方法まで深堀りして解説します。実務で使える知識を中心に、事実ベースでまとめます。

CORS とプリフライトの基本

ブラウザはセキュリティのため、異なるオリジン(スキーム、ホスト、ポートの組み合わせ)間のリソース共有を制限します。CORS はサーバーが HTTP ヘッダーでどのクロスオリジン要求を許可するかを示す仕組みです。

クロスオリジンのリクエストは大きく「単純(simple)リクエスト」と「非単純(non-simple)リクエスト」に分かれます。非単純リクエスト(例:カスタムヘッダーを付与する、Content-Type が application/json など)はプリフライト(OPTIONS メソッドによる予備的な問い合わせ)を必要とし、ブラウザがサーバーに対して Access-Control-Request-Headers や Access-Control-Request-Method を送ります。サーバーはそれに応答して Access-Control-Allow-Headers や Access-Control-Allow-Methods、Access-Control-Allow-Origin を返すことで、その後の本リクエストが許可されるかをブラウザに伝えます。

Access-Control-Allow-Headers の役割と仕様

Access-Control-Allow-Headers は、サーバーがクライアント(ブラウザ)から来るリクエストに含まれるカスタムヘッダーや許可するリクエストヘッダー名の一覧を HTTP レスポンスで示すためのヘッダーです。プリフライト応答に含められ、値はカンマ区切りのヘッダー名リスト(例:"Content-Type, Authorization, X-Requested-With")になります。

ポイント:

  • ブラウザはプリフライトの Access-Control-Allow-Headers を見て、実際のリクエストで指定されたヘッダーが許可されているか判断します。許可されていないヘッダーがあると、ブラウザは本リクエストを送信しません。
  • ヘッダー名は大文字小文字を区別しない(case-insensitive)ですが、サーバー側での比較実装に注意してください。
  • Access-Control-Allow-Headers にワイルドカード("*")は標準仕様上の一般的な利用では使いません。特にブラウザ挙動は厳密で、'*' を用いることは推奨されません。代わりにプリフライトで送られてきた Access-Control-Request-Headers をそのままエコーする形で応答することが一般的です。

Access-Control-Allow-Headers とその他ヘッダーの関係

混同しやすいヘッダーとの違いを整理します。

  • Access-Control-Allow-Origin: どのオリジンからのアクセスを許可するかを示します。これがなければ CORS は成立しません。
  • Access-Control-Allow-Methods: プリフライトで許可する HTTP メソッド(GET, POST, PUT など)を列挙します。
  • Access-Control-Expose-Headers: レスポンスに含まれるレスポンスヘッダーのうち、JavaScript からアクセス可能にするものを列挙します(request 側のヘッダーとは別物)。
  • Access-Control-Allow-Credentials: Cookie や HTTP 認証情報などの資格情報を含むリクエストを許可するかを示します。true にすると Access-Control-Allow-Origin にワイルドカードを使えません。

プリフライトの流れ(簡潔な一連)

  • ブラウザは実際のリクエストを送る前に、OPTIONS メソッドでプリフライトを送る。ヘッダーに Access-Control-Request-Method, Access-Control-Request-Headers を含む。
  • サーバーは OPTIONS を受け取り、CORS ポリシーに基づいて Access-Control-Allow-* ヘッダーで応答する。
  • ブラウザはプリフライト応答を評価し、許可されていれば本リクエストを送る。許可されていなければエラーとなる。

よくあるエラーメッセージと原因

  • "Request header field X is not allowed by Access-Control-Allow-Headers.":クライアントが送ろうとしたヘッダーがサーバーの Access-Control-Allow-Headers に含まれていない。
  • プリフライトが 404/405 を返す:サーバーが OPTIONS を正しく処理していない(ルーティングやミドルウェアで OPTIONS を除外している等)。
  • ヘッダーが CDN やリバースプロキシで消される:中間のプロキシが Access-Control-Allow-* ヘッダーを削除している可能性。

実践例:Express(Node.js)での設定

シンプルな例:

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Custom-Header');
  if (req.method === 'OPTIONS') return res.sendStatus(204);
  next();
});

注意点:Express では公式の cors ミドルウェア(npm の cors パッケージ)を使うと動作や細部の挙動を安全に扱えます。dynamic に Access-Control-Request-Headers を反映させたい場合は、レスポンスにそのままコピーする実装がよく使われますが、必ずホワイトリスト基準で制約を入れるべきです。

実践例:Nginx / Apache 設定例

Nginx の例(プリフライトを許可):

location /api/ {
  if ($request_method = 'OPTIONS') {
    add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
    add_header 'Access-Control-Max-Age' 86400 always;
    return 204;
  }
  add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
}

Apache の例(.htaccess または conf):

<IfModule mod_headers.c>
  Header set Access-Control-Allow-Origin "https://example.com"
  Header set Access-Control-Allow-Methods "GET,POST,OPTIONS"
  Header set Access-Control-Allow-Headers "Authorization,Content-Type"
</IfModule>

注意:プロダクションでワイルドカードを乱用したり、認証情報を含むリクエストでいきなり "*" を使う設定は危険です。Access-Control-Allow-Credentials: true と "*" の組合せは許されません。

セキュリティ考察とベストプラクティス

  • 最小特権原則:許可するヘッダーを最小限にする。"Authorization" やカスタム認証ヘッダーを許可する際は、サーバー側で必ず認証・検証を行う。
  • Origin のホワイトリスト化:Access-Control-Allow-Origin は可能な限り固定のオリジンを返す。動的にオリジンを返す場合は、入力の検証を厳密に行い、Vary: Origin ヘッダーを付与してキャッシュの誤動作を防ぐ。
  • プリフライトのキャッシュ:Access-Control-Max-Age を設定するとプリフライト応答をブラウザがキャッシュしますが、過度に長くすると誤った許可が長時間残るリスクがあるため適切な値を選ぶ(例:600〜86400 秒の範囲で運用に合わせて)。
  • ミドルウェア/プロキシ対応:CDN やロードバランサー、API ゲートウェイがヘッダーを上書き/削除することがあるため、必ず実環境で検証する。

デバッグ手順

  • ブラウザの DevTools → Network タブで OPTIONS(プリフライト)と実リクエストのヘッダー・レスポンスを確認する。プリフライトに Access-Control-Allow-Headers が含まれているかをチェック。
  • エラーメッセージを確認:コンソールにはブラウザが出す CORS 関連のエラーが表示される。例:"Request header field X is not allowed by Access-Control-Allow-Headers."
  • サーバーログで OPTIONS リクエストが到達しているかを確認。ルーティングでブロックされていないかをチェック。
  • CDN/プロキシ経由なら中継ポイントでヘッダーが消されていないか検証する。(curl -I で直接オリジンサーバーに問い合わせるのも有効)

実用的なワークフロー例

フロントエンドで fetch を使って application/json を送信する場合、ブラウザはプリフライトを行います。サーバーは少なくとも "Content-Type" と、もしトークンを Authorization ヘッダーで渡すなら "Authorization" を Access-Control-Allow-Headers に含める必要があります。

注意すべき特殊ケース

  • リダイレクト:プリフライトや本リクエストがリダイレクトされた場合、ブラウザの挙動が複雑になります。リダイレクト先でも適切な CORS ヘッダーが必要です。
  • 非ブラウザクライアント:curl やサーバーサイドの HTTP クライアントは CORS 制約を受けません。CORS はブラウザ実装によるクライアント側の制御です。サーバーは常にサーバーサイドのアクセス制御を行う必要があります。
  • ヘッダー名の検証:サーバーが Access-Control-Request-Headers をそのままコピーする場合、期待しないヘッダーが許可されないようホワイトリスト化することを推奨します。

まとめ:運用上のチェックリスト

  • 許可が必要なリクエストヘッダーを洗い出す(例:Content-Type, Authorization, X-Requested-With)
  • サーバーで OPTIONS(プリフライト)に 204/200 を返すようにする
  • Access-Control-Allow-Headers に必要なヘッダー名を列挙するか、検証付きで Access-Control-Request-Headers をエコーする
  • Access-Control-Allow-Origin を最小限のオリジンに限定し、必要なら Vary: Origin を付与する
  • 中間のプロキシや CDN がヘッダーを消さないことを確認する

参考文献