warden-omniauth でログイン管理
warden-omniauth でログイン管理
概要
omniauth-twitter, warden と見てきましたが、この2つをつなげてみたいですね。 というかこれが最初はやりたかったこと。
gem にそのものズバリ warden_omniauth というのがありますが、これが結構曲者でapi.twitter.com にリクエストループを起こしてしまったりします。 なので、そのソースを追いながら、自分で両者を組み合わせていく、ということをやっていきます。
処理の流れ
- GET /auth/twitter で OAUTH をし /auth/twitter/callback に(認証成功なら)帰る (omniauth-twitter)
- そのコールバックから strategy で anthenticate! させる (warden)
いってみればそれだけです。
/auth/twitter/callback の実装
/auth/twitter => OAUTH は OmniAuth::Builder すればやってくれますから、コールバックのところを書いてみます。
get '/auth/twitter/callback' do env['warden'].authenticate! redirect '/' end
まずはここから。
strategy の定義
use Warden::Manager do |manager| manager.scope_defaults :default, :strategies => [:o_twitter], :action => 'auth/failur manager.failure_app = self end Warden::Strategies.add(:o_twitter, WardenOmniTwitter::Strategy)
strategy として:o_twitterを指定し(:omni_twitter は WardenOmniAuth で使ってるから)、 valid? と authenticate! を実装するクラス WardenOmniTwitter::Strategy (次に作る)を :o_twitter に対応するものとして追加します。
まず strategy の実装を見ると、
class WardenOmniTwitter class Strategy < Warden::Strategies::Base def authenticate! if user = _auth2user(env['omniauth.auth']) success!(user) else redirect! "/auth/twitter" end end def _auth2user(auth) return if auth.nil? user = { :provider => auth.provider, :uid => auth.uid, :credentials => auth.credentials, :info=>auth.info } return user end
Warden::Strategies::Base から派生させ、 authenticate!() で、env['omniauth.auth'] から必要な情報を抽出し、ハッシュに落とします(_auth2user())。env['omniauth.auth'] が空だったら nil が返り失敗、成功すればそのハッシュが success!() 経由で env['warden'].user にセットされます。
まずここまでで動作確認します。動くはず。
コールバックの処理をミドルウェアで
一応これで動くは動くのですが、/auth/twitter/callback の処理を吸収したいところ。 それにはミドルウェアを一枚かませて routing 処理をします。rackミドルウェアにするには、
- initialize(app) で app を取る
- call(env) で env をうけ、[code, {header}, [body]] を返す
を守ればいいです。
class WardenOmniTwitter ... def initialize(app) @app = app end def call(env) @app.call(env) end end
これを作っておいて、自アプリ内で use するだけです。
... Warden::Strategies.add(:o_twitter, WardenOmniTwitter::Strategy) use WardenOmniTwitter
ただし、use OmniAuth::Builder の後にしてください。じゃないとenv['omniauth.auth'] がうまくとれなくてうまくうごきません。
で、call(env)をいじくっていきます。基本的には、request.path_info が /auth/twitter/callback だったら認証をみて、されていればてきとーなところにリダイレクト、されてなかったら /auth/twitter に飛ぶ、ということをします。
def call(env) req = Rack::Request.new(env) res = Rack::Response.new if req.path =~ /^\/auth\/twitter\/callback$/ if env['warden'].authenticate? res.redirect("/") else res.redirect("/auth/twitter") end res.finish else @app.call(env) end
env['warden'].authenticate? と ? がついてるのは、既に認証されてたら true を返し、 されてなかったら認証をしにいきます(winnning strategy の authenticate! を走らす)。
なので、認証されてなかったら /auth/twitter に認証しにいき、oauth が終わって /auth/twitter/callback に返ってきたらここで拾って authenticate! を走らせて env['omniauth.auth'] から env['warden'].user にユーザー情報をセットしsuccess!(user)、 で / にリダイレクト、と。 ほんとはリダイレクト先は redirect_after_callback= で指定できるようにすべき。
ここまでで同じく動くはず。
Warden-OmniAuth
gem に登録されてるのと、git で見られるは、微妙に違うんですね。git の方だと、
class Strategy < Warden::Strategies::Base ... def authenticate! if user = (env['omniauth.auth'] || env['rack.auth'] || request['auth']) # TODO: Fix.. Completely insecure... do not use this will look in params for the auth. Apparently fixed in the new gem
とあるけど、gem のほうだと 最初の env['omniauth.auth'] || 外されており、 いつも空が返ってしまいループします。修正して require すればなんとか動くんですが、 いいのかよくわかりません。 request['auth'] を見るのはヤバいと思うけど、env['omniauth.auth'] なら大丈夫だと思うんだけど。。。
warden-omniauth を使うには、strategy を :omni_twitter にして、use WardenOmniAuth すれば動きます。redirect_after_callback も指定できるし。
参考サイト
- OmniAuth と Warden を WardenOmniAuth で連携してみた - present : http://tnakamura.hatenablog.com/entry/20120211/sinatra_warden_omniauth_wardenomniauth