はじめに
開発中、APIへのリクエストでCORSエラーに遭遇しました。
原因を調べて、リクエスト元が「許可するオリジン」として登録されていなかったことが分かったので、 https://example.com を許可設定に追加し、無事エラー解消!!
...と思いきや、今度は https://example.com:10443 というポート番号だけが違うURLから、再びCORSエラーが発生しました。
ポート番号が違うのもダメなのか!とCORSについてなんとなくしか理解してなかったことに気づいたので、基本から整理します。
CORSとは
まずはじめに、CORSの定義についてです。
CORS(Cross-Origin Resource Sharing)とは、異なるオリジン間でリソースを共有するための仕組みです。
オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) は、 HTTP ヘッダーベースの仕組みを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。
そもそもオリジンとは
オリジンは、スキーム (プロトコル)、 ホスト (ドメイン)、 ポート番号 の組み合わせで決まります。
https :// example.com : 10443 /login
└─┬─┘ └────┬────┘ └─┬─┘ └─┬──┘
スキーム ホスト ポート番号 パス(Originには含まない)
(プロトコル) (ドメイン)
Origin = スキーム + ホスト + ポート番号
この3つがすべて一致したとき、同じオリジンとして扱われます。
下記3つの例はすべて別オリジンです。
//schemeが違う
http://example.com
https://example.com
//hostが違う
https://example.com
https://api.example.com
//portが違う
http://localhost:3000
http://localhost:8080
つまり、リクエスト元とスキーム/ホスト/ポート番号のいずれかが異なるAPIにリクエストする場合、API側でリクエスト元のオリジンが許可されていないと、CORSエラーが発生します。
はじめに触れた、「https://example.com を許可設定に追加したが、 https://example.com:10443でエラーが発生した」のは、ポート番号が違う=異なるオリジンだから、ということがわかりました。
※ポート番号を書いていない場合でも、実際にはデフォルトのポート(https なら443、http なら80)が使われている
なぜ異なるオリジンへの通信が制限されるのか
では、なぜ異なるオリジンへの通信が制限されるのでしょうか?
ブラウザには、同一オリジンポリシー(Same-Origin Policy) というセキュリティ上の仕組みがあります。
これは、あるオリジンから読み込まれた文書やスクリプトが、別のオリジンのリソースへ自由にアクセスすることを制限するものです。
この制限に引っかかったときに表示されるのが、CORSエラーです。
もしこの制限がなければ、悪意のあるWebサイトから別サイトの情報を勝手に読み取られる、といったことが起こり得ます。
そのため、ブラウザは基本的に異なるオリジン間のアクセスを制限しています。
しかし実際のWebアプリケーションでは、
フロントエンド
https://example.com
↓ APIリクエスト
バックエンド
https://api.example.com
のように、異なるオリジン間で通信したいケースは普通にあります。
そこで登場するのがCORSです。
CORSは、同一オリジンポリシーによる制限を、サーバー側が許可した範囲で緩めるための仕組みと言えます。
CORSでアクセスが許可される流れ
CORSでは、サーバーがHTTPレスポンスヘッダーを使って、「このオリジンからのアクセスは許可します」
とブラウザに伝えます。
リクエスト側では、ブラウザが自動で Origin ヘッダーを付けます。
Origin: https://example.com
サーバー側でこのOriginが許可されていれば、次のようなレスポンスヘッダーを返します。
Access-Control-Allow-Origin: https://example.com
ここで大事なのは、CORSのチェックをしているのはサーバーではなくブラウザだという点です。
許可されていないOriginからのリクエストでも、サーバーはリクエストを受け取り、処理してレスポンスを返すことがあります。(例:Preflight(後述)が発生しないリクエストの場合など)
ただし、そのレスポンスに Access-Control-Allow-Origin が含まれていなければ、ブラウザがレスポンスをJavaScriptに渡さずブロックします。
ブラウザ
|
| Origin: https://example.com
v
APIサーバー
|
| レスポンスは返す
| (ただし Access-Control-Allow-Origin がない)
v
ブラウザ
|
| 許可ヘッダーがないのでブロック
v
CORSエラー
Preflightとは
CORSについて調べていると「Preflight」というものが出てきます。
Preflightリクエストは、実際のリクエストを送信する前に行われるOPTIONS メソッドの「事前確認」です。
異なるオリジンへのリクエストの中には、ブラウザがいきなり本来のリクエストを送るのではなく、
「このオリジンから、このHTTPメソッドやヘッダーを使ってリクエストしてもよいですか?」
と先にサーバーへ確認するものがあります。
Preflightでは、下記3つのHTTPリクエストヘッダーを用いて、ブラウザから次のような情報が送られます。
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
これに対してサーバーは、
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type
などを返します。
ブラウザはこのレスポンスを確認し、問題がなければ実際のリクエストを送信します。
逆に、Preflightで許可が得られなければ、実際のリクエストは送信されません。
まとめ
これまでは、CORSエラーが出たら許可設定を確認して解決してしまっていましたが、なぜその設定が必要なのか、どの単位で設定が必要なのか理解できました。
これからも1つのエラーからちゃんと仕組みを理解し、前提となる様々な概念を学んでいきたいですね!
