【セキュリティ】CORSの仕組みについて理解しよう
はじめに
現代のWebアプリケーション開発において、クロスオリジンリソース共有(Cross-Origin Resource Sharing、略してCORS)は避けて通れない重要な概念です。単なる技術的な制約ではなく、Webのセキュリティモデルの中核をなす仕組みとして、理解する必要があります。CORSは、異なるオリジン間でのリソース共有を可能にしながら、同時に悪意のある攻撃からユーザーを保護するという、一見矛盾する目的を巧みに実現しています。
この記事では、CORSの仕組みについてわかりやすく解説します。
CORSの基本概念:オリジン
CORSを理解する上で最初に押さえるべきは「オリジン」という概念です。
Webのセキュリティモデルにおいて、オリジンはスキーム(プロトコル)、ホスト名(ドメイン)、ポート番号の組み合わせで定義されます。
例えば、https://example.com:443
とhttp://example.com:80
は異なるオリジンと見なされますし、https://api.example.com
とhttps://www.example.com
もサブドメインが異なるため別のオリジンです。
このオリジンモデルは、Webの歴史において重要なセキュリティ境界を形成してきました。ブラウザは同一オリジンポリシー(Same-Origin Policy)によって、デフォルトでは異なるオリジン間のリソースアクセスを制限します。この制限があるからこそ、悪意のあるサイトがユーザーの別のタブで開いている銀行サイトのデータを読み取ることが防げるのです。
しかし、現代のWebアプリケーションでは、複数のオリジンに分散したリソースを連携させる必要が頻繁にあります。
例えば、フロントエンドのJavaScriptが異なるドメインのAPIサーバーと通信したり、CDNから静的リソースを取得したりするケースです。CORSはこのような正当なクロスオリジンリクエストを安全に実現するためのメカニズムなのです。
CORSの仕組み: プリフライトリクエストと実際のリクエスト
CORSの動作は、単純なリクエストと複雑なリクエストで異なります。
単純なリクエストとは、GET、HEAD、POSTのいずれかのメソッドで、かつ特定のヘッダーしか使用せず、コンテンツタイプもapplication/x-www-form-urlencoded、multipart/form-data、text/plainのいずれかに限られる場合です。
このようなリクエストは直接送信され、サーバーからの応答ヘッダーによってアクセス可否が判断されます。
一方、PUTやDELETEメソッドの使用、あるいはカスタムヘッダーの追加、application/jsonなどのコンテンツタイプの指定など、条件から外れるリクエストは「複雑なリクエスト」と見なされます。
この場合、ブラウザは実際のリクエストを送信する前に、OPTIONSメソッドを使った「プリフライトリクエスト」を送信します。この事前リクエストによって、実際のリクエストが安全に送信可能かどうかを確認するのです。
プリフライトリクエストには、Originヘッダーと共にAccess-Control-Request-Method(実際のリクエストで使用するHTTPメソッド)やAccess-Control-Request-Headers(実際のリクエストで使用するカスタムヘッダー)が含まれます。サーバーはこれらの情報を検証し、適切なCORSヘッダーを含む応答を返します。ブラウザはこの応答をチェックして、実際のリクエストを続行するかどうかを決定します。
※https://developer.mozilla.org/ja/docs/Web/HTTP/Guides/CORS より引用
サーバー側のCORS設定
CORS設定の主要なHTTPレスポンスヘッダー
Access-Control-Allow-Origin
これは、どのオリジン(ドメイン、プロトコル、ポートの組み合わせ)からのリクエストを許可するかをサーバーがブラウザに伝えるためのヘッダーです。
- (ワイルドカード) を指定すると、すべてのオリジンからのアクセスを許可しますが、この場合、Cookieなどの認証情報を含むリクエストは送信できません。
- 特定のオリジンを許可する場合は、
https://example.com
のように具体的なURLを記述します。 - 複数のオリジンを許可したい場合は、リクエストのOriginヘッダーをサーバー側で確認し、許可するオリジンであれば、そのオリジンをAccess-Control-Allow-Originの値として動的に設定する必要があります。
Access-Control-Allow-Methods
これは、プリフライトリクエスト(実際のデータ送信前に行われる事前確認リクエスト)に対して、サーバーが許可するHTTPメソッド(例: GET, POST, PUT, DELETE)をブラウザに伝えるヘッダーです。
Access-Control-Allow-Headers
これもプリフライトリクエストに対する応答で、クライアントがリクエストに含めることを許可するHTTPヘッダー(例: X-Requested-With, Content-Type)を指定します。
Access-Control-Expose-Headers
このヘッダーは、ブラウザのJavaScriptからアクセスを許可するレスポンスヘッダーを指定します。
デフォルトでは、Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma の6つの基本的なヘッダーしかJavaScriptからアクセスできません。これら以外のカスタムヘッダーをJavaScriptで読み取りたい場合に設定します。
認証情報を含むリクエストの扱い
Access-Control-Allow-Credentials: true
CookieやHTTP認証情報など、認証情報を含むリクエストを許可する場合にこのヘッダーを設定します。
このヘッダーを使用する場合、Access-Control-Allow-Originにはワイルドカード(*)を使用できません。 必ず具体的なオリジンを指定する必要があります。
プリフライトリクエストの最適化
Access-Control-Max-Age
このヘッダーは、プリフライトリクエストの結果をブラウザがキャッシュする時間を秒単位で指定します。
これを設定することで、同じオリジンからの連続するリクエストに対して毎回プリフライトリクエストを行う必要がなくなり、パフォーマンスが向上します。
よくあるCORSエラーとその解決策
典型的なCORSエラーにはいくつかのパターンがあります。
例えば、「Access-Control-Allow-Originヘッダーが存在しない」というエラーは、サーバーがCORSヘッダーを全く返していない場合に発生します。これはサーバー側の設定が不足していることが原因です。「Access-Control-Allow-Originの値がリクエストのOriginと一致しない」というエラーは、許可されているオリジンが限られている場合に、許可されていないオリジンからアクセスしようとした際に発生します。
プリフライトリクエストに関連するエラーも頻繁に見られます。「プリフライトリクエストのレスポンスにAccess-Control-Allow-Methodsが含まれていない」というエラーは、サーバーがOPTIONSメソッドへの応答で適切な許可メソッドを返していない場合です。
「リクエストに含まれるヘッダーがAccess-Control-Allow-Headersで許可されていない」というエラーは、カスタムヘッダーを使用しているのにサーバーがそれを明示的に許可していない場合に発生します。
これらのエラーを解決するには、まずブラウザの開発者ツールでネットワークリクエストを詳細に確認することが重要です。どのようなリクエストが送信され、どのような応答が返っているのかを正確に把握することで、問題の根本原因を特定できます。サーバー側の設定を見直し、必要なCORSヘッダーが適切に設定されているかを確認します。特に複数のオリジンに対応する必要がある場合や、認証情報を扱う場合には、設定がより複雑になるため注意が必要です。
テストとデバッグ:CORS問題を効率的に解決する方法
CORS関連の問題を効率的にデバッグするには、体系的なアプローチが必要です。
まず、ブラウザの開発者ツールのネットワークタブで、リクエストとレスポンスの流れを詳細に観察します。プリフライトリクエストが発生しているか、適切なCORSヘッダーが返されているかを確認します。Postmanやcurlのようなツールで直接APIを呼び出してみると、ブラウザの制約なしにAPIの動作を確認できます。
サーバー側のログを確認し、リクエストが実際に到達しているか、どのようなレスポンスを返しているかを調べます。特に、OPTIONSメソッドのリクエストが正しく処理されているかを重点的に確認します。
ローカル開発環境では、ブラウザのセキュリティ制限を一時的に緩和する拡張機能を使用することもできますが、本番環境では機能しないため、あくまで開発補助としてのみ使用します。
クロスオリジンリクエストが失敗した場合、エラーメッセージには詳細な情報が含まれていることが多いため、注意深く読み解きます。最近のブラウザはCORSエラーについて比較的わかりやすいメッセージを表示するようになっていますが、根本原因を理解するためには、やはりHTTPプロトコルレベルで何が起こっているかを把握することが不可欠です。
まとめ
CORSは現代のWeb開発において避けて通れない重要な技術です。適切に実装することで、異なるオリジン間で安全にリソースを共有できるようになりますが、誤った設定はセキュリティ上の脆弱性やユーザー体験の悪化につながります。本記事で解説した原則を理解し、実際のプロジェクトに適用することで、安全かつ効率的なクロスオリジン通信を実現していきましょう!
最後までお読みいただき、ありがとうございました。
参考・画像引用元URL
Discussion