omniauthのコードリーディングをしてみたので備忘録。

omniauthはRackミドルウェアとして提供されており、 /auth/:provider や /auth/:provider/callback のパスへのアクセスをフックしてIdPへのAuthorization RequestやCallback時の処理を定義することで、ソーシャルログインを実現しています。

OmniAuth::Builderについて

omniauthの設定はOmniAuth::BuilderをRackミドルウェアとして設定しつつ、ブロックで各プロバイダの設定をします

OmniAuth::BuilderはRack::Builderを継承したクラスで、複数のミドルウェア(ストラテジ)を一つのミドルウェアとしてまとめる役割があります。

Rack::Builderにブロックが渡されると、instance_evalによって、そのブロックがRack::Builderのインスタンスのコンテキストで実行されます。よってブロック内のproviderの呼び出しはOminiAuth::Builderのproviderメソッドを呼び出すことになります。providerメソッドではuseメソッドを使って、各ストラテジのクラスをミドルウェアとして登録します。

OmniAuth::Builderがミドルウェアとしてcallメソッドが呼び出されるとto_app.callが呼び出されます。

to_appはRack::Builderのto_appメソッドでuseで登録したミドルウェアからメインのアプリケーションをデコレートしたインスタンスを生成します。

たとえばtwitterとfacebookのproviderを以下のように設定したとします。

そうすると、OmuniAuth::Builder#to_appで作成されるRackアプリケーションは、以下のようにミドルウェアで順にラップ(ネスト)されたものになります。

全体のuse呼び出しの順番が Middleware1 → Middleware2 → OmniAuth → Middleware3 の場合はRack::Builder#to_appは以下のようなネストしたRackアプリケーションになります。

OmniAuth::Strategy

OmniAuthのストラテジクラスは OmniAuth::Strategyをインクルードする必要があります。

OmniAuth::Strategyのcallメソッドは以下のようにパスやHTTPメソッドを見て各処理にdispatchされます。

OAuth2.0やOpenID Connectによる認証の場合は、Authorization Requestのrequest_callメソッドとコールバックURLでToken Requestをするためのcallback_callメソッドが呼び出されます。

request_pathとcallback_pathは以下のようなコードで生成されます。options[:request_path]やoptions[:callback_path]に値が入っていなければ “#{path_prefix}/#{name}” がrequest_path、”#{path_prefix}/#{name}/callback” が callback_pathになります。path_prefixはデフォで/authになります。これがomniauthの /auth/:provider や /auth/:provider/callbackのルーティングの正体です。

request_callやcallback_callではそれぞれrequest_phase、callback_phaseメソッドを呼び出します。この{request,callback}_phaseメソッドに各プロバイダ固有の処理を入れます。

例えば、omniauth-oauth2ではrequest_phaseでAuthorization Request用のURLを発行してリダイレクト処理をしています。

callback_phaseはStrategyに既にメソッドが定義されているので、callback側で特別な処理を入れる場合はsuperを呼び出します。

omniauth-oauth2だと以下のようなコードになっており、エラーハンドリングやstateのチェックをしつつ、Authorization CodeからToken Requestによってアクセストークンを取得しています。

このようにRackミドルウェアを使って、特定のパスへのリクエストをフックしてリダイレクトを噛ませたり、あるいは外部サービスとHTTPリクエストを送ってトークンやユーザ情報をenvに入れてコントローラ側でrequest.envで各データを取得できるようにしています。