CORSの仕組みをGIFアニメで分かりやすく解説
Post on:2020年8月20日
クロスオリジンのリクエストを安全にするための同一生成元ポリシーとオリジン間のリソース共有(CORS)の仕組みをGIFアニメで解説した記事を紹介します。
✋🏼🔥 CS Visualized: CORS
by Lydia Hallie
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
- はじめに
- ✋🏼同一生成元ポリシー(Same-Origin Policy)とは
- 🔥クライアントサイドのCORS
- 💻サーバーサイドのCORS
- 🚀プリフライト リクエスト(Preflighted Requests)
- 🍪認証
はじめに
「Access to fetched to fetched has been blocked by CORS policy error」と赤い文字がコンソールに表示されると、デベロッパーなら誰でもフラストレーションが貯まりますよね!😬 このエラーを素早く回避する方法はいくつかありますが、本日は当たり前のことをそのままにしないようにしましょう!
CORSが実際に何をしているのか、なぜわたし達の味方なのか見てましょう👏🏼
❗️この記事ではHTTPの基本的なことは説明しません。HTTPリクエストとHTTPレスポンスについて詳しく知りたい場合は、以前の記事をご覧ください🙂
HTTP/2の代わりにHTTP/1.1を使用していますが、CORSには影響しません。
フロントエンドでは、別の場所にあるデータを表示したいことがよくあります。このデータを表示する前に、ブラウザはまずデータを取得するためにサーバーにリクエストしなければなりません。クライアントは、サーバーにデータを送り返すためにサーバーが必要とするすべての情報を含むHTTPリクエストを送信します🙂
サーバー(api.mywebsite.com)からユーザー情報をサイト(www.mywebsite.com)に取得しようとしているとします。
サーバーからデータを取得
これは簡単ですね!😃 サーバーにHTTPリクエストを送っただけで、リクエストしたJSONデータが返ってきました。
まったく同じリクエストを別ドメインで試してみましょう。
www.mywebsite.comからのリクエストではなく、www.anotherdomain.comにあるサイトからリクエストを行います。
別ドメインからHTTPリクエストを送る
おや、これは何が起きているのでしょうか?!
同じリクエストを送ったのですが、今度はブラウザにエラーが表示されました。
これがCORSです!💪🏼
なぜこのエラーが発生したのか、具体的に何を意味しているのか見てみましょう。
✋🏼同一生成元ポリシー(Same-Origin Policy)とは
Webは同一生成元ポリシー(Same-Origin Policy)と呼ばれるものを実施します。デフォルトでは、リクエストのオリジンと同じオリジン(same origin)にあるリソースにしかアクセスできません!💪🏼
例えば、https://mywebsite.com/image1.pngにある画像をロードしても問題はありません。
リソースが異なる(サブ)ドメイン、プロトコル、ポートにある場合、そのリソースはクロスオリジン(cross origin)になります!
同じオリジン(same origin)とクロスオリジン(cross origin)
なぜ同一生成元ポリシーが存在するのでしょうか?
同一生成元ポリシーが存在しなかったとしましょう。
叔母がFacebookで送ってきたウィルスリンクを誤ってクリックしてしまいました。このリンクは銀行サイトをロードするiframeが埋め込まれた「悪質なサイト」にリダイレクトし、設定されたクッキーによって正常にログインしてしまいます😬
この「悪質なサイト」の運営者は銀行サイトのDOMコンテンツを操作して、あなたに代わって自分の口座に送金することを可能にします。
悪質なサイトの手口
これはセキュリティのリスクが高いです!
誰もがすべてにアクセスできるようにしたくありません😧
幸いなことに、同一生成元ポリシーがここでわたし達を助けてくれます!
同一生成元ポリシーは、同じオリジン(same origin)からのリソースにのみアクセスできるようにするからです。
同一生成元ポリシーがあるから大丈夫
この場合、オリジンのwww.evilwebsite.comがwww.bank.comからクロスオリジンのリソースにアクセスしようとしています。しかし、同一生成元ポリシーがこれを阻止し、悪質なサイトの運営者がわたし達の銀行データにアクセスできないようにしてくれます🥳
では、それがCORSと何の関係があるのでしょうか?
🔥クライアントサイドのCORS
同一生成元ポリシーは実際にはスクリプトにのみ適用されますが、ブラウザはJavaScriptリクエストに対してこのポリシーを拡張しました。デフォルトでは、同じオリジンから取得したリソースにのみアクセスできます!
同じオリジンから取得したリソースにのみアクセスできる
でも、うーん、クロスオリジンのリソースにアクセスしないといけないことが多いんですよね🤔 もしかしたら、データをロードするために、フロントエンドがバックエンドのAPIとやり取りする必要があるのかもしれませんね? クロスオリジンのリクエストを安全に許可するためには、ブラウザはCORSと呼ばれるメカニズムを使用します!🥳
CORSとはCross-Origin Resource Sharingの略です。ブラウザは同じオリジンではないリソースへのアクセスを許可していませんが、CORSを利用することで、セキュリティ上の制限を少し変更して、安全にアクセスすることができます🎉
ユーザーエージェント (ブラウザなど) はCORSのメカニズムを利用して、HTTPレスポンスの特定のCORS固有のヘッダの値に基づいて、ブロックされていたクロスオリジンのリクエストを許可できます✅
クロスオリジンのリクエストが行われると、クライアントは自動的にHTTPリクエストにOriginというヘッダを追加します。Originヘッダの値は、リクエストがどこから来たのかを表します。
クロスオリジンの時に、Originヘッダが追加される
ブラウザがクロスオリジンのリソースへのアクセスを許可するためには、サーバーがクロスオリジンのリクエストを許可するかどうかを指定するサーバーのレスポンスからの特定のヘッダが必要です!
💻サーバーサイドのCORS
サーバー開発者は、HTTPレスポンスにヘッダを追加することで、クロスオリジンのリクエストが許可されていることを確認できます。ヘッダはすべてAccess-Control-*で始まります🔥 これらのCORSレスポンスヘッダの値に基づいて、ブラウザは通常は同一生成元ポリシーによってブロックされていた特定のクロスオリジンのレスポンスを許可できるようになります!
わたし達が使用できるCORSヘッダはいくつかありますが、クロスオリジンのリソースへのアクセスを許可するためにブラウザが必要とするヘッダはAccess-Control-Allow-Originです🙂
このヘッダの値は、サーバーからリクエストしているリソースへのアクセスを許可するオリジンを指定します。
https://mywebsite.comがアクセスできるサーバーを開発している場合は、そのドメインをAccess-Control-Allow-Originヘッダに追加します。
ドメインをヘッダに追加
これで完了です!🎉
サーバーがクライアントに送り返すレスポンスにこのヘッダが追加されました。このヘッダを追加したことで、https://mywebsite.comからリクエストを送信した場合、同じポリシーのオリジンがhttps://api.mywebsite.comオリジンにあるリソースの受信を制限しなくなります!
リソースの受信を制限しなくなる
ブラウザ内のCORSのメカニズムは、Access-Control-Allow-Originヘッダの値がリクエストによって送信されたOriginの値と等しいかどうかをチェックします🤚🏼
この場合、リクエストの発信元はhttps://www.mywebsite.comで、Access-Control-Allow-Originレスポンスヘッダにリストされています!
発信元とヘッダに記載された値が等しいかチェック
完璧です!🎉
これでクロスオリジンのリソースを無事受け取ることができました。
では、Access-Control-Allow-Originにリストにないオリジンからこれらのリソースにアクセスしようとするとどうなるでしょうか?🤔
リストにないオリジンからアクセス
そうです、CORSは時々とてもイライラするエラーを表示します!
しかし、わたし達はもうそのエラーが意味のあるものであることが分かります。
1 2 3 |
The 'Access-Control-Allow-Origin' header has a value 'https://www.mywebsite.com' that is not equal to the supplied origin. |
この場合、提供されたオリジンはhttps://www.anotherwebsite.comです。しかし、Access-Control-Allow-Originヘッダの許可されたオリジンのリストにはこのオリジンがありません。CORSはリクエストのブロックに成功し、コード内のフェッチされたデータにはアクセスできません😃
CORS では、許可されるオリジンの値としてワイルドカード*を追加することもできます。つまり、すべてのオリジンからのリクエストは、リクエストされたリソースにアクセスできるはずなので、注意が必要です!
Access-Control-Allow-Originは、提供できる数多くのCORSヘッダの1つです。サーバー開発者はサーバーのCORSポリシーを拡張して、特定のリクエストを許可(無視)することができます💪🏼
もう一つの一般的なヘッダーは、Access-Control-Allow-Methodsヘッダです! CORSはリストされたメソッドで送信された場合にのみ、クロスオリジンのリクエストを許可します。
Access-Control-Allow-Methodsヘッダ
この場合、GET, POST, PUTメソッドのリクエストのみが許可されます! 他のメソッド(PATCHやDELETEなど)はブロックされます❌
他のCORSヘッダとは何か、それらの使用目的は何か気になる場合は、このリストをご覧ください。
PUT, PATCH, DELETEのリクエストといえば、CORSは実際にはこれらのリクエストを異なる方法で処理します!🙃 これらの「シンプルではない」リクエストは、プリフライト リクエスト(Preflighted Requests)と呼ばれるものを開始します!
🚀プリフライト リクエスト(Preflighted Requests)
CORSのリクエストには、シンプル リクエストとプリフライト リクエストの2種類があります。どちらなのかは、リクエスト内のいくつかの値に依存します(心配しないでください、これを覚える必要はありません)。
リクエストがGETまたはPOSTメソッドで、カスタムヘッダがない場合、リクエストはシンプルです! その他のリクエスト、例えば、PUT, PATCH, DELETEメソッドを使用したリクエストはプリライトされます。
シンプル リクエストにするためにはどのような要件を満たせばよいか知りたい場合、MDNに便利なリストが用意されています!
では、プリフライト リクエストとはどういう意味でしょうか? なぜこれが発生するのでしょうか?
実際のリクエストが送信される前に、クライアントはプリフライト リクエストを生成します! プリライト リクエストには、実際のリクエストに関する情報はAccess-Control-Request-*ヘッダに含まれています🔥
これにより、ブラウザが実行しようとしている実際のリクエストに関する情報がサーバーに提供されます。リクエストのメソッドは何か、追加のヘッダは何かなどです。
プリフライト リクエストで情報がサーバーに提供される
サーバーはこのプリフライト リクエストを受信し、サーバーのCORSヘッダを含む空のHTTPレスポンスを返します。ブラウザはCORSヘッダ以外のデータを含まないプリフライト レスポンスを受信し、HTTPリクエストを許可する必要があるかどうかをチェックします✅
HTTPリクエストを許可する必要があるかどうかをチェック
この場合は、ブラウザは実際のリクエストをサーバーに送信し、サーバーは要求されたデータを返します。
ブラウザは実際のリクエストを送信し、サーバーは要求されたデータを返す
ただし、そうでない場合はCORSはプリフライト リクエストをブロックし、実際のリクエストは送信されません✋🏼 プリフライト リクエストはCORSポリシーが有効に(まだ)なっていないサーバー上のリソースにアクセスしたり、変更したりするのを防いでくれる優れた方法です! サーバーは潜在的に望まないクロスオリジンのリクエストから保護されるようになりました😃
💡 サーバーへのラウンドトリップ数を減らすために、CORSリクエストにAccess-Control-Max-Ageヘッダを追加して、プリフライト レスポンスをキャッシュできます! これにより、ブラウザは新しいプリフライト リクエストを送信する代わりに使用することができます!
🍪認証
クッキー、認証ヘッダ、TLSサーバー証明書は、デフォルトでは同一生成元のリクエストにのみ設定されます! ただし、クロスオリジンのリクエストでこれらの認証を使用したい場合もあるでしょう。例えば、ユーザーを識別するためにサーバーが使用できるリクエストにクッキーを含めたいとします。
CORSにはデフォルトで含まれていませんが、Access-Control-Allow-CredentialsCORSヘッダを追加することで、変更できます!🎉
クロスオリジンのリクエストにクッキーや他の認証ヘッダを含めたい場合は、リクエストでwithCredentialsフィールドをtrueに設定し、レスポンスにAccess-Control-Allow-Credentialsヘッダを追加する必要があります。
クロスオリジンのリクエストで認証したい場合
これで完成です!
クロスオリジンのリクエストに認証を含めることができました🥳
CORSのエラーでイライラすることもあると思いますが、ブラウザでクロスオリジンのリクエストを安全に作成できることは驚くべきことです✨
同一生成元ポリシーとCORSには、この記事でカバーできなかったことがたくさんあります。この記事やW3Cの仕様など、素晴らしいリソースがたくさんありますので、もっと知りたい方はご覧ください💪🏼
不明点があれば、@lydiahallieまで気軽に連絡してください!😊
sponsors