omniauth(と,少しomniauth-oauth2) を読む
前にも一回読んだけどあんまり理解できなかった.最近RackとかOAuthについてわかりを得てきたのでもう一度読んでみようと思った.気づいたら rails server
command を読んでいたりして(rails s を読む)少し遠回り気味だったけど,OAuth2.0の認証フローに合わせてomniauthの挙動をたどってみた.
Rackに関してはRackとは何か - Qiitaが,OAuthについては色々な OAuth のフローと doorkeeper gem での実装 - Qiitaの認可コードの項がとても参考になった.
Client Application で /app/:provider にアクセスがあると,omniauth が Rack middleware としてこのリクエストを拾い,認可フローが開始される.
middleware の使用は config/initializers/omniauth.rb
で宣言する.
Rails.application.config.middleware.use OmniAuth::Builder do provider :developer unless Rails.env.production? provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET'] end
Rails.application.config.middleware.use
の引数に OmniAuth::Builder
を指定することで,Blockの中身を Rack::Builder
ではなく OmniAuth::Builder
の DSL として処理してる(多分).
このブロック中の provider
は, Rack::Builder
をラップしている OmniAuth::Builder
で提供されてる.
def provider(klass, *args, &block) # ... begin middleware = OmniAuth::Strategies.const_get(OmniAuth::Utils.camelize(klass.to_s).to_s) rescue NameError raise(LoadError.new("Could not find matching strategy for #{klass.inspect}. You may need to install an additional gem (such as omniauth-#{klass}).")) end args.last.is_a?(Hash) ? args.push(options.merge(args.pop)) : args.push(options) use middleware, *args, &block end
例えば
provider :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET']
みたいに書くと,
twitter strategy を探しにいって,あれば use
してmiddleware の stack につんでくれる.
omniauth では, omniauth-twitter など,omniauth にのせる OAuth 用の middleware を strategy とよぶ.strategyを作る時は, OmniAuth::Strategies
namespace 以下に定義することになっていて,これによって OmniAuth::Strategies.const_get
で持ってこれるようになってる.
omniauth には 開発時の placeholder としてシンプルな developer strategy が用意されている.
strategy の本体 OminAuth::Strategy
は omniauth/omniauth.rbで autoload されてる.Rack middleware なのでおもむろに call
を見に行く.Omniauth::Strategy#call!
def call!(env) # rubocop:disable CyclomaticComplexity, PerceivedComplexity # ... @env = env @env['omniauth.strategy'] = self if on_auth_path? return mock_call!(env) if OmniAuth.config.test_mode return options_call if on_auth_path? && options_request? return request_call if on_request_path? && OmniAuth.config.allowed_request_methods.include?(request.request_method.downcase.to_sym) return callback_call if on_callback_path? return other_phase if respond_to?(:other_phase) @app.call(env) end
色々やってるけどOAuthの本筋部分をたどると,
return request_call if on_request_path? && OmniAuth.config.allowed_request_methods.include?
細かい条件は省くけど /auth/twitter
みたいなrequestが来た時, request_call
が呼ばれて Rack::Response
Object が返される.これが認可サーバの 「callback_phase
(後述)がオーバーライドされてた.omniauth-oauth2/oauth2.rb
def callback_phase # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity error = request.params["error_reason"] || request.params["error"] if error #... else self.access_token = build_access_token self.access_token = access_token.refresh! if access_token.expired? super end # ... end
build_access_token
で認可サーバにアクセストークンを要求してる
def build_access_token verifier = request.params["code"] client.auth_code.get_token(verifier, {:redirect_uri => callback_url}.merge(token_params.to_hash(:symbolize_keys => true)), deep_symbolize(options.auth_token_params)) end
callback_phase
は, callback path に返って来た時に callback_call
がよばれて,その中で呼ばれてる.
def callback_call # ... callback_phase end
super
で呼ばれてる OmniAuth::Strategy#callback_pase
はここ
def callback_phase env['omniauth.auth'] = auth_hash call_app! end
env['omniauth.auth']
に認可サーバからの情報をいれて Client Application を call して auth/twitter/callback
みたいなところに戻ってくる.
Client Applocation では, SessionController
とかで
class SessionsController < ApplicationController def create @user = User.find_or_create_from_auth_hash(auth_hash) self.current_user = @user redirect_to '/' end protected def auth_hash request.env['omniauth.auth'] end end
みたいにして,access_token とか access_token_secret をもった User record を find_or_create する.
これで, 取得した tokenを使ってリソースサーバにリクエストが送れるようになる 🎉