Rails Vue.js GraphQL実装中にCORSではまった
最近、個人開発でサーバとクライアントを分離したアプリケーションの実装をしています。構成は、RailsでAPIサーバを実装し、クライアントのVue.jsからGraphQLを介してデータのやり取りをしています。静的ホスティングサイトから配信されたVueアプリから、RailsのAPIサーバにアクセスする際に、同一オリジンポリシーとCORSの存在を知らずにはまってしまったので、振り返りを兼ねてまとめておきます。
前半部では、同一オリジンポリシーやCORSの概念についてまとめ、後半部では、その具体的な実装についてみていきます。
キーワード
Rails API, Vue.js, Vue-apollo, GraphQL, CORS, 同一オリジンポリシー, クロスオリジン通信
対象者
サーバとクライアントを分離したアプリを開発中の方
オリジン(源泉)
オリジン(源泉)とは、以下の規則に則ってURIをまとめたものです。ブラウザによって、しばしば特権の範囲として用いられます。
ある二つのURIが、同一スキーム、同一ホストおよび同一ポートを持つ場合、同一オリジンです。
(例)
- 同一のオリジン - 以下は全て同じオリジンとみなせる
http://example.com/
http://example.com:80/
http://example.com/path/file
- 異なるオリジン - 以下は異なるオリジンとみなせる
http://example.com/
http://example.com:8080/
http://www.example.com/
https://example.com:80/
https://example.com/
http://example.org/
http://ietf.org/
同一オリジンポリシー
同一オリジンポリシーとは、ブラウザ側に備わっている仕組みで、あるオリジンから読み込まれた文章やスクリプトについて、そのリソースから他のオリジンにアクセスできないように制限するものです。
制限をかけることで、リソースを、悪意を起こしかねないリソースから分離することを目的としています。
要するに、セキュリティ上、リソースを外部の干渉から防ごうとするものです。
クロスオリジン通信
クロスオリジン通信とは、ブラウザがリソースを取得したオリジンとは別のオリジンから、データを取得する仕組みのことです。
XMLHttpRequestは、JavaScriptを使用してHTTP通信を行うためのAPIです。現在、主要なブラウザ上で、HTML5と合わせて、Cross-Origin Resource Sharing(CORS)に従った、クロスオリジンでの通信に対応したXHR Level2が実装されています。また、Fetch APIと呼ばれる新規格も登場しました。
これらによって、JavaScriptを介したクロスオリジン通信が可能となりました。
しかし、XHRがクロスオリジン通信を許容したことにより、クロスオリジン通信によるCSRF攻撃が可能となりました。実装の際には気をつけた方が良さそうです。
オリジン間リソース共有(CORS)
オリジン間リソース共有(CORS:Cross-Origin Resource Sharing)は、あるオリジン上で動いているアプリケーションが、別のオリジンの上で動いているアプリケーション上のリソースに、アクセスできるようにする仕組みです。
異なるオリジン間のアクセス制限を解除するために、OriginやAccess-Control-Allow-Originヘッダを使用します。具体的に次の節で見てみましょう。
はまったポイント
同一オリジンポリシーによって、作成したRails APIサーバからのレスポンスをブラウザが受け取ってくれませんでした。
次のようなエラーログがブラウザのコンソール上で出力されます。
Access to fetch at ‘http://localhost:3000/graphql/’ from origin ‘http://localhost:8080’ 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.
開発段階なのでlocalhostですが、それぞれポート番号が異なっているので、別のオリジンとみなされます。今後、本番環境でも、ホスティングとAPIサーバを分離する予定のため、CORSに対応する必要があります。
エラーログにも記載してあるように、サーバ側で、異なるオリジンであるlocalhost:3000だけから、アクセスを許容できるようにAccess-Control-Allow-Originで指定してあげれば良さそうです。
APIサーバ側からのレスポンスに、Access-Control-Allow-Origin: http://localhost:8080
を含めてあげることで、ブラウザ側は、このリソースからのアクセスを許されたオリジンであると判別し、同一オリジンポリシーによる制約を解除します。
私の場合、APIサーバは http://localhost:8080 からのアクセスだけに制限しますが、他のオリジン全てのアクセスを許容する場合はAccess-Control-Allow-Origin: *
をヘッダに含めてあげれば良さそうです。
RailsにおけるCORS対応
Rails側のAPIサーバ側で、CORSに対応していなかったためブラウザ側で受信を拒絶されていたようです。Railsでは gem cyu / rack-coresを使うと、簡単にCORS対応が可能になります。
Gemfileに以下の一行を追加して、bundle installをします。
Gemfile
gem 'rack-cors'
次に、config/initializers/cors.rbのコメント部分をコメントアウトします。
cors.rb
# Be sure to restart your server when you modify this file.
# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
# Read more: https://github.com/cyu/rack-cors
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:8080'
resource '*',
headers: :any,
methods: [:get, :post, :options]
end
end
originsのところで、許容するオリジンを指定してあげます。
これだけで、APIサーバ側でCORSの設定ができます。一瞬ですね。
実際に返されたレスポンスヘッダを確認してみます。
(CORS対応後)
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Expose-Headers:
Access-Control-Max-Age: 1728000
Transfer-Encoding: chunked
レスポンスヘッダの3行目に、Access-Control-Allow-Originが含まれているのがみて取れます。これで、ブラウザ側が、アクセスが許容されたリソースに対するレスポンスだと判断し
、制限されることはなくなりました。
まとめ
オリジンやオリジン間リソース共有といった、Webの根幹をなす概念についてまとめてみました。
ハマりポイントの解決法だけではなく、その技術の根底にある仕組みや概念を深掘りすることで、自分の中でも深い理解ができた気がします。