web.svg

CORS(Cross-Origin Resource Sharing)の基本

Web

はじめに

CORS は Cross-Origin Resouce Sharing の略で、「コルス」と読むことが多いです。 日本語では「オリジン間リソース共有」と訳されます。

特にフロントエンドの開発において、別のサーバーの API にアクセスする場合、CORS に引っかかってデータが取得できないといったことを経験している人は多いのではないでしょうか。

この記事では、この CORS について簡単ではありますがまとめていきます。

オリジン(Origin)とは

まず CORS に含まれる「Origin」とはなんでしょうか。

これは、URL の「プロトコル(スキーム)」、「ホスト(ドメイン)」、「ポート」の組み合わせを指します。

http :// localhost : 8080 /hogehoge/fugafuga プロトコル ホスト ポート

3 つすべてが同じ場合は、同一オリジン(Same-Origin)となります。 3 つのいずれかが異なる場合は、異なるオリジン(Cross-Origin)となります。

URL1URL2
http://example.com/hogehttp://example.com/fugaSame-Origin
http://example.comhttps://example.comCross-Origin
http://example1.comhttp://example2.comCross-Origin
http://example.com:8080http://example.com:8081Cross-Origin

同一オリジンポリシー

ブラウザには、同一オリジンポリシー(Same-Origin Policy)という仕組みがあります。 XSS(Cross-Site Scriptiong)や CSRF(Cross-Site Request Forgeries)などによって、 意図していない WEB ページ(異なるオリジン)からのリクエストによるリソースへのアクセスを防ぎます。

参考: 同一オリジンポリシー - ウェブセキュリティ | MDN

例えば、このサイトから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 ヘッダーなどにも制限がかかっているため、 これらを許可する設定が必要になることがあります。

WEBページ(A)WEBページ(B)WEBサーバー(https://hoge.com)WEBサーバー(https://fuga.com)APIサーバー(https://piyo.com)CORSの設定Origin: https://hoge.comMethods: GET, POST, PUT, DELETEHeaders: Authorization

当然のことですが、リソースへのアクセス権限として CORS の設定を行うのは、リソースを持つ側、上記の例だと API サーバーです。 しかし、リソースの取扱に関する判断を行うのは「ブラウザ」です。

混同してしまいそうですが、CORS はあくまでブラウザが判断するための情報であり、他のサーバーからのリクエストに適用されるものではありません。 極端な話、WEB サーバーがプロキシサーバーのような中継役を担えば、CORS は関係ないということです。

Request ResponseWEBサーバー(https://hoge.com)APIサーバー(https://piyo.com)

では、ブラウザはいつ 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 つのオリジンか、すべてのオリジンを表す*を設定します。

ブラウザはOriginAccess-Control-Allow-Originの値を比較し、同じあるいは*の場合に正常なレスポンスと判断します。 一致しない場合、あるいはAccess-Control-Allow-Originそのものが存在しない場合は、CORS のエラーとなります。

① リクエストOrigin: https://hoge.comAccess-Control-Allow-Origin: https://hoge.com② 結果の検証

単純リクエストを扱う場合、 1 つ注意点があります。 それは、リクエストそのものは送信してしまうことです。 サーバーは対応する処理を行い、その結果と一緒に CORS の情報を送信をしています(フレームワークや実装方法によります)。 つまり、ブラウザではエラーだが、サーバーでは正常に処理が終了するということが発生します。

これを防ぐには、サーバー側で単純リクエストを受け付けないようにします。 ブラウザは単純リクエストでない場合、プリフライトリクエストを送信するようになります。

プリフライトリクエスト

プリフライトリクエストは、本番のリクエストの前にOPTIONSメソッドのリクエストを送信することで、CORS の設定を取得します。 ブラウザは、プリフライトリクエストの結果を基に、本番のリクエストの送信を行うかを判断します。 そのため、単純リクエストのようにサーバー側の処理が実行される心配はありません。

また、フロントエンド開発者が意識的に実装する必要はなく、ブラウザが自動で送信します。 CORS の判定も同じくブラウザが自動で行います。

単純リクエストと同様に、プリフライトリクエストでもリクエストヘッダーとレスポンスヘッダーの内容を基に、CORS の判定を行います。 各ヘッダーに設定される項目は、以下となります。

① プリフライトリクエスト[OPTIONS]Origin: https://hoge.comAccess-Control-Request-Method: POSTAccess-Control-Request-Headers: AuthorizationAccess-Control-Allow-Origin: https://hoge.comAccess-Control-Allow-Method: GET, POST, PUT, DELETEAccess-Control-Allow-Headers: Authorization② 送信判断③ 本番のリクエスト
  • リクエストヘッダー(ブラウザが自動で設定)
    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-CredentialsCookie などの資格情報を使用して良いか否かを示します。(詳細は後述)
    Access-Control-Expose-HeadersJavaScript でアクセス可能なすべてのヘッダーを示します。

リソースを持つサーバーが異なるオリジンの WEB ページとやり取りする可能性がある場合は、プリフライトリクエストに対する結果を返す必要があります。 設定方法はフレームワークなどによって異なるためここでは割愛しますが、設定する必要があることは覚えておいてください。

また単純リクエストとしないために、application/jsonでやり取りする、カスタムヘッダーを使用するなどの方法が考えられます。 サーバーサイドを実装する際はこちらも意識してみてください。

異なるオリジンで Cookie を用いる場合は少し注意が必要になります。

Fetch API を用いる場合、以下のようにcredentials: 'include'を設定すれば、リクエストに Cookie が設定されるようになります。

await fetch('https://hoge.com', { credentials: 'include' })

しかし、 CORS のデフォルトの設定では、Cookie を含むリクエストは制限の対象となります。 Cookie を許可するには、レスポンスヘッダーのAccess-Control-Allow-Credentialstrueに設定する必要があります。

また設定する際も注意が必要で、Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headersの値として*を使用することができません。 どれも許可する情報を明示的に示す必要があります。 フレームワークによってはコンパイルエラーなどにしてくれますが、実装の際には注意してみてください。

おわりに

CORS について解説してきましたが、私も最初はよくわかりませんでした。 ブラウザが行うこと、サーバーが行うことを意識すれば自ずと理解できてくると思うので、実装の際には注意してみてください。