CORSをちゃんと理解する

今回はCORSについてまとめたいと思います。私はよくサーバー側の設定を忘れて、ブラウザに怒られたりしてますが、今ひとつ理解できず、コピペして終わってました。Railsapiモードでアプリを作る時とかも、よくわからずgem rack-corsとか書いてました。最近、FetchAPIについて学ぶ機会があって、ブラウザからのhttp通信についてちゃんと調べようと思ったのがきっかけでCORSについてもまとめてみました。

corsとは?

CORS(Cross-Origin Resource Sharing)は異なるオリジン間での通信を許可する仕組み。CSRFなどのセキュリティ面から、XMLHttpRequestFetchAPIなどのhttp通信は同一オリジンポリシーに従います。従ってオリジンが異なる場合はそれを許可するための、適切なcorsに関するヘッダーが必要になってきます。

オリジンとは?

オリジン = ドメイン + プロトコル + ポート番号

https://domain-a.comみたいな部分がオリジンですね。つまり、https://domain-a.comからhttps://domain-b.comへの通信はデフォルトでは許可されず、corsによる設定が必要になるという事です。

ではcorsの設定についてみていきます。

プリフライトリクエス

後述する特定の条件以外の場合は、プリフライトリクエストと呼ばれるリクエストを最初に行い、あらかじめサーバーがリクエストに応答可能か確認を行います。httpのOPTIONSリクエストによって、これを行います。

リクエス

OPTIONS / HTTP/1.1
Origin: https://domain-a.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Custom-Header, Content-Type

通信を行いたいメソッドや、ヘッダーをサーバーに対して提示します。Access-Control-Request-Headersに関しては後述の単純リクエストで許可されるヘッダー以外を指定します。

レスポンス

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://domain-a.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-Custom-Header, Content-Type
Access-Control-Max-Age: 86400

レスポンスには許可するオリジン、メソッド、ヘッダー情報が含まれます。Access-Control-Allow-Originに関してはワイルドカードの使用が可能なので、全てを許可する場合は、

Access-Control-Allow-Origin: *

と書くことが可能です。
Access-Control-Max-Ageは、プリフライトリクエストの結果をキャッシュしておける上限時間です。

単純リクエス

以下のいずれかの条件を満たす場合は、プリフライトリクエストを送らずに直接http通信を行うことが可能です。
逆に言えば、この条件を満たさない場合は、全てまず、プリフライトリクエストを行わなければなりません。

  • 以下のメソッドのいずれかである。
    GET, HEAD, POST

  • 以下のヘッダーのみを含む。
    Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width

  • Content-Typeヘッダーは以下のみ。
    application/x-www-form-urlencoded, multipart/form-data, text/plain

  • リクエストに使用される XMLHttpRequestUpload にイベントリスナーが登録されていない。

  • リクエストに ReadableStream オブジェクトが使用されていない。

資格情報(認証)

認証を行うためにCookieをヘッダーに含めて送信したい場合は多くあると思います。デフォルトではヘッダーにCookieは含めないので、リクエスト時に設定が必要です。 XMLHttpRequestの場合

const xhr = new XMLHttpRequest();
xhr.withCredentials = true;

FetchAPIの場合

fetch('https://domain-a.com', {
  mode: 'cors',
  credentials: 'include',
});

これにより、リクエストヘッダーにCookieヘッダーが含まれるようになります。
レスポンスに次のヘッダーが含まれば、許可されたことになります。

Access-Control-Allow-Credentials: true

これはプリフライトリクエストの場合でも、実際のリクエストの場合にも(もちろん単純リクエストの場合も)含まれます。プリフライトリクエストの場合は、実際のリクエストにCookieを含められるかどうか、実際のリクエストの場合は、それをブラウザで利用して良いかを定めています。つまり、実際のリクエストでこれがtrueでなかった場合は、ブラウザによって無視されるということになります。

注意点としては、資格情報を送る場合はAccess-Control-Allow-Originワイルドカードを設定できません。特定のオリジン名を指定する必要があります。

参考
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS http://www.tohoho-web.com/ex/cors.html https://qiita.com/att55/items/2154a8aad8bf1409db2b