Access-Control-Expose-Headers徹底解説 — CORSでのレスポンスヘッダ公開と実践設定ガイド

はじめに

フロントエンドとバックエンドが別ドメインで通信する場合、ブラウザはセキュリティのために同一生成元ポリシー(Same-Origin Policy)を適用します。クロスオリジンリクエストを行う際に利用される仕組みがCORS(Cross-Origin Resource Sharing)です。本稿ではCORSにおけるレスポンスヘッダのうち、JavaScriptから参照可能にするためのキーとなるヘッダ『Access-Control-Expose-Headers』について、概念・仕様・サーバ設定例・実務上の注意点を詳細に解説します。

Access-Control-Expose-Headersとは何か

『Access-Control-Expose-Headers』は、クロスオリジンHTTPレスポンスで、ブラウザのJavaScript(fetchやXMLHttpRequest)から参照(getResponseHeaderやResponse.headers.get)できるレスポンスヘッダの一覧をサーバが指定するためのレスポンスヘッダです。サーバはカンマ区切りで列挙し、列挙されたヘッダ名だけがクライアント側で取得可能になります。

なぜ必要か:デフォルトの挙動

セキュリティ上、ブラウザはクロスオリジンのレスポンスヘッダのうち、デフォルトで一部の「セーフリスト」ヘッダのみをJavaScriptに公開しています。これらは仕様で定められており、以下のヘッダが含まれます。

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

これら以外のヘッダ(例:Content-Length、X-Request-Id、X-Total-Count、Authorization関連のヘッダなど)は、明示的に『Access-Control-Expose-Headers』で列挙されない限り、クロスオリジン環境ではブラウザから参照できません。したがって、APIが独自ヘッダでメタ情報を返す場合は、このヘッダで列挙する必要があります。

基本的な使い方(構文と制約)

  • 構文: Access-Control-Expose-Headers: Header1, Header2, ...
  • ヘッダ名は大文字小文字を区別しないが、実務上はサーバとクライアントで同じ表記にしておくと良い。
  • ワイルドカード(*)はこのヘッダでは利用できない(仕様上、Expose-Headersに'*'を使って全てを公開することはできません)。
  • このヘッダはレスポンスごとに付与でき、プリフライトとは直接関係しない(プリフライトで扱うのは主にAccess-Control-Allow-Headersなど)。

Access-Control-Allow-Headersとの違い

混同しやすいヘッダに『Access-Control-Allow-Headers』があります。こちらはプリフライト(OPTIONSリクエスト)でクライアントが送ろうとしているカスタムリクエストヘッダをサーバが許可するためのもので、リクエスト側のヘッダに関する設定です。一方で『Access-Control-Expose-Headers』はレスポンス側で、ブラウザがクライアントスクリプトに晒す(見せる)ヘッダを指定するものです。

ブラウザ側での挙動(fetchとXHRの違い)

fetch APIやXMLHttpRequestは、サーバがExpose-Headersで許可したヘッダのみを返すようブラウザが制御します。fetchを例にすると、以下のようにResponse.headers.get('X-My-Header')で取得できますが、Expose-Headersに列挙されていないヘッダはnullになります。

fetch('https://api.example.com/data')
  .then(response => {
    console.log(response.headers.get('X-My-Custom-Header'));
  });

axiosなどのライブラリも内部的にはブラウザのレスポンスヘッダAPIを使っているため、同じ制約を受けます。

サーバ側の設定例(実践)

代表的なサーバでの設定例を示します。注意点として、Nginxなど一部のサーバではadd_headerの挙動(ステータスコードによりヘッダ追加されない等)に注意が必要です。

Node.js / Express

// 単純な例
app.use((req, res, next) => {
  res.set('Access-Control-Allow-Origin', 'https://example.com');
  res.set('Access-Control-Expose-Headers', 'X-My-Custom-Header, X-Another-Header');
  next();
});

Nginx

# server または location ブロック内
add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
add_header 'Access-Control-Expose-Headers' 'X-My-Custom-Header, X-Another-Header' always;

# 注意: nginx の add_header はデフォルトで一部ステータスにしか適用されないため 'always' を付けることを推奨

Apache (mod_headers)

Header set Access-Control-Allow-Origin "https://example.com"
Header set Access-Control-Expose-Headers "X-My-Custom-Header, X-Another-Header"

# .htaccess や vhost に記述

AWS S3(バケットのCORS設定)

S3ではCORS設定XML内の<ExposeHeader>要素で指定します。

<CORSConfiguration>
  <CORSRule>
    <AllowedOrigin>https://example.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <ExposeHeader>X-My-Custom-Header</ExposeHeader>
  </CORSRule>
</CORSConfiguration>

実務上のユースケース

  • ページネーション情報の提供:APIが総件数をレスポンスヘッダ(例:X-Total-Count)で返す場合、Expose-Headersで列挙しておくとフロントでヘッダを読み取れる。
  • リクエストIDやトレースIDの参照:デバッグやログ連携のためにバックエンドが返すトレースIDを参照したい場合。
  • カスタムメタ情報の取得:バージョンやデプロイ識別子など、ボディを変えずに付与したい情報。

セキュリティと運用上の注意点

  • 不要なヘッダを公開しない:内部情報や機密性の高いヘッダを不用意にExposeすると情報漏洩のリスクがある。公開するヘッダは最小限にする。
  • ワイルドカードは使えない:Expose-Headersに'*'相当で全てを公開する指定はできないため、明示的に列挙する必要がある(結果として安全側に立つ仕様)。
  • Access-Control-Allow-Originとの整合:Expose-Headersは単独で設定可能だが、クロスオリジンアクセスを許すならAccess-Control-Allow-Originや必要に応じてAccess-Control-Allow-Credentialsも適切に設定する。Credentialsを使う場合はAllow-Originに'*'を使えない制約に注意。
  • キャッシュとヘッダの一貫性:CDNやプロキシがヘッダを書き換える可能性があるため、デプロイやキャッシュ設定を確認する。Nginxのようにエラー応答にヘッダが付与されないケースもチェックする。

よくあるトラブルと対処法

  • ヘッダが取得できない: ブラウザのデベロッパーツールで実際のレスポンスヘッダを確認し、サービス側がExpose-Headersを付与しているか確認する。レスポンスにExpose-Headersがあっても、列挙しているヘッダ名が誤っている(スペルミス)ケースが多い。
  • Nginxで設定しているのに付かない: add_headerの適用ステータスや 'always' オプション、または別の上位プロキシがヘッダを除去していないかを確認。
  • CORSエラーが出る: コンソールログのエラー文を確認し、Access-Control-Allow-Originやプリフライト(OPTIONS)の許可が必要かを判断する。Expose-Headersはレスポンスヘッダを読むための設定で、プリフライトのAllow-Headersとは用途が異なる。

ブラウザ互換性と仕様の裏側

Expose-Headersは主要ブラウザでサポートされていますが、細かな実装差や過去のバグが存在するため、重要なプロダクション機能で使う場合は対象ブラウザでテストしてください。仕様上、特に古いブラウザやIE系はCORS実装が異なるため注意を払う必要があります。Cookieや認証情報の扱いはExpose-Headersとは別に、credentials関連の設定が関わります。

まとめとベストプラクティス

Access-Control-Expose-Headersは、クロスオリジンのレスポンスヘッダをクライアントJavaScriptから安全に参照可能にするための重要な手段です。実装時は以下を心がけてください。

  • 公開が必要なヘッダのみを明示的に列挙する。
  • サーバ(およびプロキシ/CDN)の設定ミスでヘッダが消えることがあるため、デバッグ時はブラウザのネットワークタブで実際のヘッダを確認する。
  • Expose-HeadersはプリフライトのAllow-Headersとは別物であることを理解する。
  • CredentialsやAllow-Originの設定と組み合わせた全体設計を行う(特に認証系処理)。

参考文献