CORS(Cross-Origin Resource Sharing)の基本
はじめに
CORS は Cross-Origin Resouce Sharing の略で、「コルス」と読むことが多いです。 日本語では「オリジン間リソース共有」と訳されます。
特にフロントエンドの開発において、別のサーバーの API にアクセスする場合、CORS に引っかかってデータが取得できないといったことを経験している人は多いのではないでしょうか。
この記事では、この CORS について簡単ではありますがまとめていきます。
オリジン(Origin)とは
まず CORS に含まれる「Origin」とはなんでしょうか。
これは、URL の「プロトコル(スキーム)」、「ホスト(ドメイン)」、「ポート」の組み合わせを指します。
3 つすべてが同じ場合は、同一オリジン(Same-Origin)となります。 3 つのいずれかが異なる場合は、異なるオリジン(Cross-Origin)となります。
URL1 | URL2 | |
---|---|---|
http://example.com/hoge | http://example.com/fuga | Same-Origin |
http://example.com | https://example.com | Cross-Origin |
http://example1.com | http://example2.com | Cross-Origin |
http://example.com:8080 | http://example.com:8081 | Cross-Origin |
同一オリジンポリシー
ブラウザには、同一オリジンポリシー(Same-Origin Policy)という仕組みがあります。 XSS(Cross-Site Scriptiong)や CSRF(Cross-Site Request Forgeries)などによって、 意図していない WEB ページ(異なるオリジン)からのリクエストによるリソースへのアクセスを防ぎます。
例えば、このサイトからhttps://google.com
に対して Fetch API でリクエストを送信してもエラーになります。
しかし、https://google.com
からであれば以下リクエストは成功します。
試したい方はディベロッパーツールで以下のコードを実行すれば結果を取得できると思います。
await fetch('https://google.com')
Access to fetch at 'https://google.com/' from origin 'https://b1san-blog.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
しかし、例えば WEB サーバーと API サーバーを分けて構築するなど、 異なるオリジンのリソースへアクセスすることはあり得ます。 そこで、異なるオリジンのリソースにアクセスできるようにする仕組みが CORS です。
CORS とは
CORS は異なるオリジンからのリクエストに対し、自身のリソースへのアクセス権限をブラウザに知らせるための仕組みです。 ブラウザは受け取ったアクセス権限を基に、レスポンスの受信、あるいはリクエストの送信を行うかを判断します。
先程例に出した WEB サーバーと API サーバーで考えた場合、API サーバー側が 対象となる WEB サーバーからのリクエストを許可すれば、 異なるオリジンでも通信できるということです。
実際にはオリジンの設定だけでは不十分なケースもあり、 アクセス方法となる HTTP メソッドやアクセス時の情報を示す HTTP ヘッダーなどにも制限がかかっているため、 これらを許可する設定が必要になることがあります。
当然のことですが、リソースへのアクセス権限として CORS の設定を行うのは、リソースを持つ側、上記の例だと API サーバーです。 しかし、リソースの取扱に関する判断を行うのは「ブラウザ」です。
混同してしまいそうですが、CORS はあくまでブラウザが判断するための情報であり、他のサーバーからのリクエストに適用されるものではありません。 極端な話、WEB サーバーがプロキシサーバーのような中継役を担えば、CORS は関係ないということです。
では、ブラウザはいつ CORS の情報を取得しているのでしょうか。 これを理解するには、単純リクエスト(Simple Request) と プリフライトリクエスト(Preflight Request) について知る必要があります。
単純リクエスト
単純リクエストとは、以下の要素で構成されたリクエストのことです。
PUT
メソッドやContent-Type
としてapplication/json
などを扱う場合は、単純リクエストには該当しません。
- HTTP メソッド
GET
,HEAD
,POST
- Http ヘッダー
Accept
,Accept-Language
,Content-Language
,Content-Type
,Range
- Content-Type
application/x-www-form-urlencoded
,multipart/form-data
,text/plain
例えば、Fetch API で単純にfetch('https://hoge.com')
とした場合は単純リクエストに該当します。
単純リクエストの場合、ブラウザは異なるオリジンでもリクエストを送信してレスポンスを取得します。
そのリクエストとレスポンスに含まれる情報を見て、有効なレスポンスであることを判断します。
具体的には、リクエストヘッダーに含まれるOrigin
とレスポンスヘッダーに含まれるAccess-Control-Allow-Origin
の値で判断します。
Origin
はこれまでにもあったように、送信元の WEB ページのオリジンを指します。
基本的にはブラウザが自動的に設定してくれるため、それほど意識する必要はありません。
Access-Control-Allow-Origin
は、リソースを提供する側がアクセスを許可しているオリジンを示すものです。
値として、許可している 1 つのオリジンか、すべてのオリジンを表す*
を設定します。
ブラウザはOrigin
とAccess-Control-Allow-Origin
の値を比較し、同じあるいは*
の場合に正常なレスポンスと判断します。
一致しない場合、あるいはAccess-Control-Allow-Origin
そのものが存在しない場合は、CORS のエラーとなります。
単純リクエストを扱う場合、 1 つ注意点があります。 それは、リクエストそのものは送信してしまうことです。 サーバーは対応する処理を行い、その結果と一緒に CORS の情報を送信をしています(フレームワークや実装方法によります)。 つまり、ブラウザではエラーだが、サーバーでは正常に処理が終了するということが発生します。
これを防ぐには、サーバー側で単純リクエストを受け付けないようにします。 ブラウザは単純リクエストでない場合、プリフライトリクエストを送信するようになります。
プリフライトリクエスト
プリフライトリクエストは、本番のリクエストの前にOPTIONS
メソッドのリクエストを送信することで、CORS の設定を取得します。
ブラウザは、プリフライトリクエストの結果を基に、本番のリクエストの送信を行うかを判断します。
そのため、単純リクエストのようにサーバー側の処理が実行される心配はありません。
また、フロントエンド開発者が意識的に実装する必要はなく、ブラウザが自動で送信します。 CORS の判定も同じくブラウザが自動で行います。
単純リクエストと同様に、プリフライトリクエストでもリクエストヘッダーとレスポンスヘッダーの内容を基に、CORS の判定を行います。 各ヘッダーに設定される項目は、以下となります。
- リクエストヘッダー(ブラウザが自動で設定)
Origin: https://hoge.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Authorization, Custom-Header
ヘッダー 説明 Origin
自身のオリジンを示します。 Access-Control-Request-Method
リクエストで使用する HTTP メソッドを示します。 Access-Control-Request-Headers
リクエストで使用するすべての HTTP ヘッダーを示します。 - レスポンスヘッダー
Access-Control-Allow-Origin: https://hoge.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-allow-Headers: Authorization, Custom-Header Access-Control-Max-Age: 600 Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: Custom-Header
ヘッダー 説明 Access-Control-Allow-Origin
リクエストを許可しているオリジンを示します。
値として 1 つのオリジンまたはすべてを許可する*
を設定します。Access-Control-Allow-Methods
リクエスト方法として許可している HTTP メソッドを示します。
すべてを許可する場合は*
を使用します。Access-Control-Allow-Headers
リクエストに使用できる HTTP ヘッダーを示します。
すべてを許可する場合は*
を使用します。Access-Control-Max-Age
プリフライトリクエストのキャッシュ時間(秒)を示します。
Chromium v76 以降はデフォルト 2 時間(7200 秒)です。-1
を設定することでキャッシュを無効にします。Access-Control-Allow-Credentials
Cookie などの資格情報を使用して良いか否かを示します。(詳細は後述) Access-Control-Expose-Headers
JavaScript でアクセス可能なすべてのヘッダーを示します。
リソースを持つサーバーが異なるオリジンの WEB ページとやり取りする可能性がある場合は、プリフライトリクエストに対する結果を返す必要があります。 設定方法はフレームワークなどによって異なるためここでは割愛しますが、設定する必要があることは覚えておいてください。
また単純リクエストとしないために、application/json
でやり取りする、カスタムヘッダーを使用するなどの方法が考えられます。
サーバーサイドを実装する際はこちらも意識してみてください。
Cookie
異なるオリジンで Cookie を用いる場合は少し注意が必要になります。
Fetch API を用いる場合、以下のようにcredentials: 'include'
を設定すれば、リクエストに Cookie が設定されるようになります。
await fetch('https://hoge.com', { credentials: 'include' })
しかし、 CORS のデフォルトの設定では、Cookie を含むリクエストは制限の対象となります。
Cookie を許可するには、レスポンスヘッダーのAccess-Control-Allow-Credentials
をtrue
に設定する必要があります。
また設定する際も注意が必要で、Access-Control-Allow-Origin
、Access-Control-Allow-Methods
、Access-Control-Allow-Headers
の値として*
を使用することができません。
どれも許可する情報を明示的に示す必要があります。
フレームワークによってはコンパイルエラーなどにしてくれますが、実装の際には注意してみてください。
おわりに
CORS について解説してきましたが、私も最初はよくわかりませんでした。 ブラウザが行うこと、サーバーが行うことを意識すれば自ずと理解できてくると思うので、実装の際には注意してみてください。