この記事を一言でいうなら、「闘いの記録」です。ここ数ヶ月、とある大規模Webアプリの保守運用に関わっているのですが、その中でCORS対応にどっぷり浸かることになりました。 正直に白状すると、これまでの開発人生でCORSをあんまり意識してこなかったというか、中身についてはお恥ずかしながら詳しくなかったんです。そんなわけで、今回は基礎中の基礎から泥臭く調べ直しました。この記事はその時の「調査メモ」を自分なりに整理してまとめたものです。正直なところ、ネットにある情報を自分なりに解釈し直しただけなので、「わざわざ世に出すほどの内容かな?」と迷うラインではありました。でも、せっかく七転八倒してまとめたので、供養のつもりで公開しちゃいます。ちなみに、普段の技術ネタはQiitaに書くというマイルールがあるのですが、今回の記事はあまりに「自分用の格闘メモ」すぎるので、この個人ブログにひっそりと置いておくことにしました。
はじめに
Webブラウザには「同一オリジンポリシー」という基本的なセキュリティルールが実装されており、異なるドメイン間でのデータ通信が制限されています。
例えば、xxx.com上で動作するJavaScriptからyyy.comのAPIにPOSTリクエストを送信しようとすると、エラーが発生します。
しかし、実際の運用においては、異なるドメイン間で通信を許可する必要が生じる場合があります。これを実現する仕組みがCORS(Cross-Origin Resource Sharing)です。
概要
1. ブラウザによるプリフライトリクエストの送信
ブラウザは、クロスオリジンで「安全でない」とみなされるリクエスト(POSTやPUTなどGET以外のメソッド、カスタムヘッダー付きのリクエスト等)を送信する際、事前にOPTIONSメソッドによるプリフライトリクエストを送信します。
2. サーバーによるCORSレスポンスヘッダーの送信
サーバーはCORS関連のHTTPヘッダーを付与してレスポンスを返します(つまり、サーバー側で設定が必要になります)。
3. ブラウザによるレスポンスヘッダーの検証
ブラウザは、以下の条件を確認します。
- Access-Control-Allow-Origin
- ヘッダーの値が送信元オリジン(例: https://xxx.com)と一致しているか、またはワイルドカード(*)が指定されているか。
- Access-Control-Allow-Methods
- リクエストで指定したHTTPメソッド(例: POST、PUTなど)が許可されているか。
- Access-Control-Allow-Headers
- リクエストで使用するカスタムヘッダー(Content-TypeやAuthorization等)が許可されているか。
これらの条件がすべて満たされていれば、リクエストは許可されたと判断されます。条件を満たさない場合、リクエスト自体が送信されず、JavaScript側でエラーとなります。
4. 本来のリクエストの送信
ブラウザが条件をすべて満たしていると判定した場合、実際のリクエスト(GET, POST, PUTなど)が送信されます。
5. サーバーによるリクエスト処理とCORSレスポンスヘッダーの付与
サーバーは、リクエストが同一オリジンかクロスオリジンかに関係なく、通常どおりリクエスト内容を処理します。ただし、レスポンスにもCORS関連のヘッダーを付与する必要があります。
6. ブラウザによる最終判定とJavaScriptからのアクセス制御
ブラウザはレスポンスヘッダーを確認し、Access-Control-Allow-Originが自身のオリジンと一致していれば、JavaScriptからレスポンス本文にアクセスできます。一致しない場合、レスポンス本文へのアクセスはブロックされ、CORSエラーとなります。
発生条件
CORSは、JavaScriptによるクロスオリジンのAPIアクセス(fetch、XMLHttpRequest、画像の読み込みなど)に対するセキュリティ制約です。フォーム送信やリンククリックによるページ遷移には適用されません。例えば、JavaScriptでフォーム送信を実行した場合、CORS認証やプリフライトリクエストは発生しません。サーバー側でリダイレクトやエラーが返されることはありますが、CORSエラー(JavaScriptによるレスポンスのブロック)は生じません。
fetch APIにおけるレスポンス種別
fetch APIを利用してクロスオリジンリクエストを行う際、CORSの設定やリダイレクトの有無によって、返されるレスポンスの「種別(typeプロパティ)」が変化します。これにより、JavaScriptからアクセスできる情報の範囲が制限されます。
リクエストの状況に応じたレスポンス種別の判定フローは以下の通りです。

レスポンス種別の詳細
判定された各 typeによって、JavaScriptからアクセスできる情報の範囲が以下のように制限されます。
- basic
- 同一オリジンへのリクエスト、またはCORSを介さないリクエストで返されます。レスポンスボディ、ヘッダー、ステータスコードのすべてにアクセス可能です。
- cors
- CORSが正しく許可されたクロスオリジンリクエストで返されます。レスポンスボディと、特定の制限されたヘッダー(および
Access-Control-Expose-Headersで許可されたもの)にアクセスできます。
- CORSが正しく許可されたクロスオリジンリクエストで返されます。レスポンスボディと、特定の制限されたヘッダー(および
- opaque
no-corsモードで送信されたリクエストの結果です。セキュリティ上の理由から、レスポンスの内容(ボディやヘッダー)は一切読み取れず、ステータスコードも一律で0となります。
- opaqueredirect
- リダイレクト(3xx)が発生し、そのリダイレクト先がクロスオリジンである場合に返されることがあります。
opaque同様、内容へのアクセスは制限され、ステータスコードは0になります。
- リダイレクト(3xx)が発生し、そのリダイレクト先がクロスオリジンである場合に返されることがあります。
Tips
- 異なるドメインへの通信でも、プリフライトリクエストが送信される場合と送信されない場合があります。詳細については、以下のURLを参照してください。
- Chromeではセキュリティ上の理由から、開発者ツールのネットワークタブでプリフライトリクエストの内容を確認できません。プリフライトリクエスト自体が送信されているかどうかも確認できないため、専用ツールや拡張機能の導入が必要です。なお、Firefoxの開発者ツールでは確認可能です。
- 本リクエストのレスポンスが3xx(リダイレクト)ステータスの場合、ブラウザはリダイレクト先に自動的にリクエストを送信します。リダイレクト先がクロスオリジンである場合、リダイレクト先のサーバーでもCORSヘッダーを返す必要があります。ヘッダーが返されない場合、ブラウザはそのレスポンスをブロックし、JavaScriptから結果を取得できません。