There's an echo in my head

日々のメモ。

OmniAuthでの動的なパスとOpenID認証

とりあえずメモ。

:request_pathオプションと:callback_pathオプション

OmniAuthでproviderを宣言する際に:request_pathオプションにProcを渡すことで、デフォルトだと/auth/:providerとなっているリクエストフェーズ開始のパスを動的に設定できる。

たとえば次のようにすると、パスが/auth/fooで終わってるリクエストはすべてfooプロバイダでのリクエストフェーズであるとして処理されるようになり、たとえば/quwup/auth/fooとかでもヒットする。

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :foo, request_path: -> { |env|
    # パスそのものではなく、このリクエストがリクエストフェーズ開始のもので
    # あるか否かをBooleanで返すようにするのがポイント
    request = Rack::Request.new(env)
    request.end_with?("/auth/foo")
  }
end

このへんの処理はOmniauth::Strategy#on_request_path?あたりで行われている。

同様にコールバックのパスも、:callback_pathオプションにProcを渡すことで動的に設定できる。

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :foo, on_callback_path: -> { |env|
    # パスそのものではなく、このリクエストがコールバックフェーズ開始のもので
    # あるか否かをBooleanで返すようにするのがポイント
    request = Rack::Request.new(env)
    request.end_with?("/auth/foo/callback")
  }
end

このへんの処理はOmniAuth::Strategy#on_callback_path?あたりで行われている。on_request_path?よりも複雑なことをやっている。

コールバックのパスは動的に作られない

前述の例のようにrequest_pathcallback_pathにProcを渡した場合、それが使われてるのはあくまで今来たリクエストがリクエストフェーズのものもしくはコールバックフェーズのものであるかを判定しているに過ぎず、リクエストのenvに応じて文字列を作り出しているわけではない。

そこで厄介になるのが、プロバイダ側に送信するコールバックのパスはそのままだと動的に作れないということだ。

omniauth-openidではリクエストフェーズで呼ばれたOmniAuth::Strategy#callback_pathの返り値を含んだOmniAuth::Strategy#callback_urlの返り値がreturn_toパラメータとしてプロバイダ側に送信される。

このcallback_pathの決定が難儀なもので、リクエストフェーズでのことなので2行目のoptions[:callback_path].call(env)はtrueになることはなく、あれよあれよと/auth/foo/callback"みたいに決め打ちされる。分岐はあるけど外からいじれる感じではない。

このため:callback_pathオプションでカジュアルにいじると、OpenIDとかで事前にプロバイダに送っておいた決め打ちのcallback_urlと、認証が終わってリダイレクトされてきたカジュアルなURLとがマッチしないということが発生する。つらい。

モンキーパッチ当てるとか、SCRIPT_NAMEをいじるとか

じゃあ実際どうするのよというと、OmniAuth::Strategy#callback_urlにモンキーパッチをあててしまうのが手っ取り早い。

module OmniAuthCasualCallbackUrl
  def callback_url
    # ちなみにenvも使える
    full_host + script_name + "/quwup/auth/foo/callback" + query_string
  end
end

module OmniAuth::Strategy
  prepend OmniAuthCasualCallbackUrl
end

もしくはprefixを付けたいぐらいであれば、before_callback_phaseでSCRIPT_NAMEをいじるのも有りかもしれない。envをいじるので微妙な気はするけども…。

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :foo, on_callback_path: (env)-> {
    # パスそのものではなく、このリクエストがコールバックフェーズ開始のもので
    # あるか否かをBooleanで返すようにするのがポイント
    request = Rack::Request.new(env)
    request.end_with?("/auth/foo/callback")
  }
end

OmniAuth.configure do |config|
  config.before_request_phase = (env)-> {
    env["SCRIPT_NAME"] = "quwup"
  }
end
このブログに出てくるコードスニペッツは、引用あるいは断りがない限りMITライセンスです。