CORS完全ガイド:仕組み・主要ヘッダ・プリフライトと安全なサーバー設定

CORSとは — 概要

CORS(Cross-Origin Resource Sharing、クロスオリジンリソース共有)は、ウェブブラウザにおける「あるオリジン(スキーム+ホスト+ポート)から読み込まれたページが別のオリジンのリソースへ安全にアクセスするための仕組み」です。ブラウザはデフォルトで「同一オリジンポリシー(Same-Origin Policy)」を適用し、異なるオリジン間の読み書きを制限します。CORSはその制限を、安全に緩和するためにサーバー側で許可情報を付与する標準的な方法です。

なぜ必要か:同一オリジンポリシーの背景

  • 同一オリジンポリシーは、悪意あるサイトがユーザーの認証済みセッション(Cookieや認証ヘッダ)を利用して、他サイトのデータを盗んだり操作したりすることを防ぐために存在します。

  • しかし、APIを公開しているサービスや、CDN・外部ドメインからのリソース利用など、正当なクロスオリジン通信の需要も多くあります。CORSはその需要とセキュリティのバランスを取るための仕組みです。

基本的な仕組み(ブラウザ側とサーバー側のやり取り)

CORSは、ブラウザが送るリクエストと、サーバーが返すレスポンスに特定のHTTPヘッダを付けることで成立します。ブラウザは、リクエストの種類やヘッダに応じて「通常リクエスト(simple」)」または「プリフライト(preflight)」を行い、サーバーが返す「Access-Control-」系ヘッダを見てアクセス可否を判断します。重要なのは、CORSはブラウザが適用するポリシーであり、サーバーや他のクライアント(curl等)はCORS制約を直接受けない点です。

シンプル(Simple)リクエストとプリフライト(Preflight)

「シンプルリクエスト」は、メソッドが GET / HEAD / POST のいずれかで、使用するリクエストヘッダがブラウザの「セーフリスト」に含まれ、Content-Type が application/x-www-form-urlencoded、multipart/form-data、text/plain のいずれかである場合に該当します。これらはプリフライト不要で直接送信され、サーバーはレスポンスに Access-Control-Allow-Origin を含めるだけで良い場合が多いです。

一方、カスタムヘッダを送る、PUT/DELETE 等のメソッドを使う、または Content-Type が application/json 等の非セーフリスト値の場合は、ブラウザは事前に OPTIONS メソッドで「プリフライト」リクエストを送ります。プリフライトでは Access-Control-Request-Method、Access-Control-Request-Headers を含む OPTIONS リクエストを送り、サーバーが許可するかを確認します。

主要なCORSヘッダ(意味と使い方)

  • Access-Control-Allow-Origin — レスポンス側。許可するオリジンを示す。値は特定のオリジン(例: https://example.com)かワイルドカード (*)。なお Access-Control-Allow-Credentials: true を使う場合は "*" は使えない(単一のオリジンを明示する必要がある)。

  • Access-Control-Allow-Methods — プリフライトレスポンスで返す。許可する HTTP メソッドの一覧(GET, POST, PUT, DELETE など)。

  • Access-Control-Allow-Headers — プリフライトレスポンスで返す。リクエストで使用を許可するカスタムヘッダ名の一覧。

  • Access-Control-Allow-Credentials — レスポンス側。ブラウザに対して、資格情報(Cookie、認証ヘッダ)を含めてよいかを示す。true を返すと、クライアントは認証情報を送ったり、レスポンスが読み取れたりする(ただしブラウザ側でも fetch/XHR に credentials: 'include' 等が必要)。

  • Access-Control-Expose-Headers — レスポンス側。ブラウザの JavaScript から参照を許可するレスポンスヘッダの一覧(デフォルトでは一部ヘッダしか見えない)。

  • Access-Control-Max-Age — プリフライト結果のキャッシュ時間(秒)。ブラウザはこの期間プリフライトを省略できる。ただしブラウザや中間キャッシュが独自に上限を設けることがある。

  • Vary: Origin — サーバーが Origin に応じてレスポンスを変える場合は、このヘッダを付けて共有キャッシュが誤配信しないようにすることが推奨される。

プリフライトの具体例(ヘッダのやり取り)

クライアントがプリフライトを出す例(OPTIONS):

OPTIONS /api/data HTTP/1.1
Origin: https://client.example
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Requested-With, Content-Type

サーバーが返す例:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://client.example
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Requested-With, Content-Type
Access-Control-Max-Age: 86400
Vary: Origin

認証情報(クッキー等)とCORS

クッキーや HTTP 基本認証などを伴うクロスオリジン通信を行うには、以下が必要です:ブラウザ側で fetch/XHR に credentials: 'include'(または withCredentials=true)を設定し、サーバー側で Access-Control-Allow-Credentials: true と明示的な Access-Control-Allow-Origin(ワイルドカード不可)を返すこと。両方の条件を満たさないと、ブラウザはレスポンスを JavaScript に渡しません。

よくある誤設定とセキュリティ上の落とし穴

  • ワイルドカード + 認証情報 — Access-Control-Allow-Origin: * を設定しつつ Access-Control-Allow-Credentials: true にするのは無効/危険です。ブラウザはこの組み合わせを許さないか、期待した挙動になりません。

  • Origin の反射(reflect origin)を無批判に行う — サーバー側で来た Origin ヘッダをそのまま返す実装(ホワイトリスト確認なし)は、任意の悪意あるサイトにアクセスを許してしまう恐れがある。必ずホワイトリスト照合を行う。

  • 不要に広く許可しすぎる — 許可するメソッドやヘッダを必要最小限に限定する。Access-Control-Expose-Headers も最小限に。

  • キャッシュの扱い — 共有プロキシや CDN を使う際、Origin によって異なるレスポンスが生成される場合は Vary: Origin を付けないと別のユーザーに誤ったレスポンスが配信される可能性がある。

実用的なサーバー構成例

以下は代表的な簡易例(概念的)です。

  • パブリックAPI(認証不要、任意のオリジンから読み込み可)

    Access-Control-Allow-Origin: *
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Access-Control-Max-Age: 600
    
  • 認証付きAPI(Cookieを使う)

    Access-Control-Allow-Origin: https://app.example
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Vary: Origin
    

サーバーサイドの具体的設定は使用しているソフトウェア(Node/Express、Nginx、Apache、S3 など)によって異なるため、利用する環境の公式ガイドを参照してください。

ブラウザ固有の挙動と注意点

  • CORSはブラウザの機能であり、curl やサーバー間通信には適用されません。したがって、サーバー側で「Origin をチェックして拒否」する実装は、攻撃を完全に防げるわけではなく、サーバー側の認可ロジックが最終防御であるべきです。

  • fetch() の mode:'no-cors' を使うと、ブラウザは opaque(中身が見えない)なレスポンスを返すため、レスポンス本文や多くのヘッダにアクセスできません。

  • ブラウザはプリフライトの Access-Control-Max-Age を任意に短く抑える実装をする場合があります。長時間のキャッシュを当てにしすぎないこと。

デバッグのコツ

  • ブラウザの開発者ツールのネットワークタブで、Origin ヘッダ、プリフライト(OPTIONS)応答、Access-Control-* ヘッダを確認する。
  • サーバーのレスポンスに Vary: Origin を付けておくとキャッシュ問題の切り分けがしやすい。
  • curl では明示的に Origin ヘッダを付けて疑似的にテストできる(ただし curl 自体は CORS を強制しないので、サーバーの挙動確認用)。

まとめ(実践的な指針)

  • ブラウザ向けの安全なクロスオリジンアクセスを提供するには、CORS ヘッダを正しく設定すること。
  • 認証情報を扱う場合は Access-Control-Allow-Credentials: true と明示的なオリジン指定が必須で、ワイルドカードは避ける。
  • Origin を反射する際は必ずホワイトリスト照合を導入し、必要なメソッドとヘッダのみ許可する。
  • サーバー側の認可ロジックを最終的な防御層として設計し、CORS はブラウザに対するアクセス制御の一手段であると理解する。

参考文献