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 も指定できるし。

参考サイト