【詳解】オリジン間リソース共有(CORS : Cross-Origin-Request-Sharing)を理解する

こんばんは。今日は、オリジン間リソース共有の仕組みについて整理しておこうと思います。

今開発しているアプリケーションで、ちょうどこのCORSのエラーが発生して対処する必要があったので、その過程でCORSについて学んだことを整理しておこうと思った次第です。

それでは早速参ります!

Contents

今回の教材

今回、勉強するにあたり、以下の記事に大変お世話になりました。

Mozillaの記事

https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

Qiitaの記事

https://qiita.com/att55/items/2154a8aad8bf1409db2b

CORSの概要

CORSの概要は以下の通りです。

  • 追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組み
  • オリジンとは、https://domain-a.com:443のようにプロトコル+ドメイン+ポートの組み合わせ
  • XSS (クロスサイトスクリプティング)やCSRF(クロスサイトリクエストフォージェリー)といった悪意のある攻撃からアプリケーション・ユーザを守るために異なるオリジン間でのリソース共有を制限する必要があった(Same Origin Policy)
  • 2004年に提唱されて、2014にW3C Recommendationとして承認された。
  • CORSを実現するための方式として、Simple RequestとPreflight Requestの2パターンがある。

Mozilla公式ページより

上記のように、domain-a.com上のアプリケーション・サーバ間の通信は、同じドメイン間なので許可されるが、domain-a.comのアプリケーションからdomain-b.comのサーバへの通信はブラウザの規定では許可されない。

これを、特別なヘッダを付与することで可能とする仕組みをCORSと言います。

CORSを実現するリクエストのフロー

CORSにはSimple RequestとPreflight Requestのパターンがあると書きましたが、どのような場合にどちらのパターンになるかは、以下によって決まります。

以下の全てに当てはまる場合のみ、Simple Requestになります。それ以外の場合は、サーバに悪影響を及ぼす可能性のあるリクエストとみなされて、Preflight Requestとなります。

  • 以下メソッドのうちの一つであること。GET/HEAD/POST
  • ユーザーエージェントによって自動的に設定されたヘッダーを除いて、以下ヘッダ以外が含まれていないこと。
  • リクエストに使用されるどの XMLHttpRequestUpload にもイベントリスナーが登録されていないこと。
  • リクエストに ReadableStream オブジェクトが使用されていないこと。

Simple Request

  • クライアントからは、Originヘッダを付与して、Originを明示する
  • サーバからは、Access-Control-Allow-Originヘッダで許可するオリジン情報を返す

Mozilla公式ページより

Preflight Request

  • 最初にPOSTでもGETでもなく、何もリソースを変更できない安全なOPTIONSリクエストを送る
  • この時、クライアントからはOrigin, Access-Control-Request-Method, Access-Control-Request-Headersヘッダを付与する。
  • Access-Control-Request-Methods/Headersで、それぞれ実際のリクエストがそのメソッド/ヘッダで送られることを、サーバ側に伝えています。
  • サーバ側は、Access-Control-Allow-Origin/Methods/Headersを返す
  • これらのヘッダで、受け入れ可能なOrigin/Methods/Headerがクライアントへ伝えられている
  • この条件を満たしている場合に、初めてサーバへ正式なリクエストが送られる。
  • この時のリクエストヘッダ、及びレスポンスヘッダはSimple Requestの時と同じ。
Mozilla公式ページより

実験してみる

今回は、Preflight Reqeustのパターンについて実験してみました。クライアントからのリクエストヘッダに、上記一覧以外のヘッダを加えることで実現しています。

クライアント側のコード(JavaScript)

以下のように、JavaScriptでfetch APIを利用して、APIをたたきにいっています。

・・・
return await fetch (url, {
        method: 'POST',
        headers:{
            'Host': 'XXXX',
            'Ocp-Apim-Subscription-Key':'XXXXX',
            'Ocp-Apim-Trace': 'true',    
        },
        body:JSON.stringify(data)
    })

このリクエストの様子を開発者ツールで見てみます。

すると・・・ちゃんとUpdateという名称でPreflight Requestと実際のPOSTリクエストの両方が確認できました!

それぞれのヘッダはどうなっているでしょうか。

はじめにOPTIONSリクエストです。上記でご紹介した仕様の通り、確かにOrigin、Access-Control-Request-Headers/Methodsヘッダが自動的に付与されている様子が確認できます。

また、サーバからのResponseヘッダにもAllow-Headers/Methodsヘッダが含まれていることが確認できます。

続いて、Preflight Request後の正式リクエストです。(長いのでスクショは断念)

こちらも、上記の理論の通り、REQUESTヘッダにはOriginが、RESPONSEヘッダニハAccess-Control-XXXXヘッダが含まれていることが確認できましたね・・・!


Request URL: https:XXXXX
Request Method: POST
Status Code: 200 OK
Remote Address: XXXXX
Referrer Policy: strict-origin-when-cross-origin

RESPONSE HEADER
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Expose-Headers: Ocp-Apim-Trace-Location,Ocp-Apim-ApiId,Ocp-Apim-OperationId,Ocp-Apim-ProductId,Ocp-Apim-SubscriptionId,Ocp-Apim-UserId
Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Date: Wed, 01 Sep 2021 15:30:54 GMT
Ocp-Apim-ApiId: XXXX
Ocp-Apim-OperationId: XXXXX
Ocp-Apim-ProductId: XXXX
Ocp-Apim-SubscriptionId: XXXX
Ocp-Apim-Trace-Location: XXXX
Ocp-Apim-UserId: XXXX
Request-Context: appId=
Transfer-Encoding: chunked
Vary: Accept-Encoding

REQUEST HEADER
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Connection: keep-alive
Content-Length: 17201
Content-Type: text/plain;charset=UTF-8
Host: XXXX
Ocp-Apim-Subscription-Key: XXXX
Ocp-Apim-Trace: XXXX
Origin: http://localhost:3000
Referer: http://localhost:3000/
sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"
sec-ch-ua-mobile: ?0
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 XXXXX

以上、CORSについて学んだことのまとめでした。

最後までご覧いただきありがとうございました!

もしこの記事がお役に立ちましたら、いいねをポチっていただけますと励みになります!

おしまい

この記事を気に入っていただけたらシェアをお願いします!
ABOUT US
Yuu113
初めまして。ゆうたろうと申します。 兵庫県出身、東京でシステムエンジニアをしております。現在は主にデータ分析、機械学習を活用してビジネスモデリングに取り組んでいます。 日々学んだことや経験したことを整理していきたいと思い、ブログを始めました。旅行、カメラ、IT技術、江戸文化が大好きですので、これらについても記事にしていきたいと思っています。