rack-cors から CORS を理解する
web の仕様を理解するにはドキュメントを読み、その仕様の実装を見てみるのが良い方法だと思う。
ruby の CORS の制御を実装している rack-cors から CORS を理解する。
- CORS について
- rack-cors
CORS について
- Cross Origin Resource Sharing とは、異なるオリジン(protocol + port + domain)間で、リソースを認可するための仕様
- 主に XMLHttpRequest または Fetch API を呼び出し時に利用されることが多い(つまり JavaScript を利用したサーバーとの通信全般)
- この仕様の全体についてはまとめないので下のような記事を読むと良さそう
- Simple Request と Preflight Request に関しては、自分もあまり覚えていなかったのでまとめる
- Simple Request
- サイト間リクエストがユーザーデータに影響を与えないようなリクエスト
- CORS プリフライトを伴わないリクエストで、 MDN のドキュメントによると以下 5 条件が満たされているリクエストのこと。
- 許可されているメソッドのうちの一つであること
- 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 オブジェクトが使用されていないこと。
- 許可されているメソッドのうちの一つであること
- Preflight Request
rack-cors とは
rack アプリケーションに CORS の制御機能を追加する rack middleware。 独自の DSL で CORS を制御できるようにする。 rack-cors の README にあるサンプル。 どのオリジンが、どのリソースに、何の操作をしていいかを定義している。
use Rack::Cors do allow do origins 'localhost:3000', '127.0.0.1:3000', /\Ahttp:\/\/192\.168\.0\.\d{1,3}(:\d+)?\z/ # regular expressions can be used here resource '/file/list_all/', :headers => 'x-domain-token' resource '/file/at/*', methods: [:get, :post, :delete, :put, :patch, :options, :head], headers: 'x-domain-token', expose: ['Some-Custom-Response-Header'], max_age: 600 # headers to expose end allow do origins '*' resource '/public/*', headers: :any, methods: :get # Only allow a request for a specific host resource '/api/v1/*', headers: :any, methods: :get, if: proc { |env| env['HTTP_HOST'] == 'api.example.com' } end end
実際に見ていく
- まずはアプリケーション起動時にオリジン・リソース・許可する操作を記述した DSL を解釈して実行しやすい状態として持っておく
- DSL の解釈開始 https://github.com/nekootoko3/rack-cors/blob/6fbc109450f872bc12ab987f4b027f250b79d295/lib/rack/cors.rb#L45
Rack::Cors#allow
の内部にある DSL はResources
のコンテキストで解釈される https://github.com/nekootoko3/rack-cors/blob/6fbc109450f872bc12ab987f4b027f250b79d295/lib/rack/cors.rb#L54-L62- origins, resource を事前処理しておく
実際にリクエストが来た時、Simple Request
とPreflight Request
で制御が異なるのでそれぞれのケースを見ていく。
Simple Request のケース
- Simple Request を扱うケース
- レスポンス時に付加するヘッダーを取得する
- Rack::Cors#process_cors
- 定義した DSL の origin・resource にリクエストが発生している origin・resource の組み合わせがあるか確認する
- 存在していればレスポンス時に付加するヘッダーを取得する
- Rack::Cors::Resource
- 定義した DSL に基づいて
Access-Control-*
のハッシュを返す
- 定義した DSL に基づいて
- Rack::Cors::Resource
- レスポンス時に
Access-Control-*
のヘッダーを付加する- リクエストヘッダーの
Origin
と レスポンスヘッダーのAccess-Control-Allow-Origin
が一致していなければリソースは利用されない。 - Simple Request の時って、
Access-Control-Allow-Origin
とAccess-Control-Allow-Credentials
以外のヘッダーはいらないのでは?
- リクエストヘッダーの
Preflight Request のケース
- Preflight Request
- Method が
Options
でヘッダーにHTTP_ACCESS_CONTROL_REQUEST_METHOD
がある時は、Preflight Request としてリクエストを処理する - Rack::Cors#process_preflight
- 定義した DSL の origin・resource にリクエストが発生している origin・resource の組み合わせがあるか確認する
- なければ Preflight Request の処理を終了する。
Access-Control-*
がレスポンスヘッダーに付加されないので後続のリクエストは送られない - リクエストヘッダーの
Access-Control-Request-Method
とAccess-Control-Request-Headers
が事前に許可するとしたメソッドとヘッダーに含まれていなければ Preflight Request の処理を終了する。 - Rack::Cors#to_preflight_headers
- レスポンスヘッダーに追加する
Access-Control-*
のハッシュを返す
- レスポンスヘッダーに追加する
- 内側の rack アプリケーションにはリクエストを伝搬せず、ヘッダーを付加したレスポンスを返す
- Method が