SorceryのコールバックURLは基本的にconfigファイルで変更するのですが、コントローラから動的に変えたいときはどうすればいいのかわからなかったので、いろいろ試してみました。
力任せなやり方
こんな感じで、@provider.callback_url
にコールバック先のURLを入れる。
1
2
3
4
5
6
7
|
class OauthsController < ApplicationController
def oauth
login_at(auth_params[:provider])
# 極端なことを言うと、こんなコールバック先にもできる
@provider.callback_url = 'https://www.google.com/'
end
end
|
実際に動的な値を検証
1
2
3
4
5
6
7
8
9
10
11
12
|
class OauthsController < ApplicationController
def oauth
login_at(auth_params[:provider])
if params[:hoge] == 'hoge'
@provider.callback_url = 'http://127.0.0.1:3000/oauth/callback?provider=github&hoge=hoge'
elsif params[:hofe] == 'foo'
@provider.callback_url = 'http://127.0.0.1:3000/oauth/callback?provider=github&hoge=foo'
else
# 特に指定なし
end
end
end
|
1
2
3
|
= link_to 'Login with NoParams', auth_at_provider_path(provider: :github)
= link_to 'Login with Hoge', auth_at_provider_path(provider: :github, hoge: 'hoge')
= link_to 'Login with Foo', auth_at_provider_path(provider: :github, hoge: 'foo')
|
これで試したところ、callbackアクションに渡されるparams[:hoge]
は
Login with NoParams……前回の値
Login with Hoge……"hoge"
Login with Foo……"foo"
となる。
Sorceryのコード
この記事ではGitHub認証を挙げるが、他の認証でも差異はそれほどなさそう。
Sorceryのexternal
を使うことで、下記のモジュールがコントローラにincludeされることになっている。
sorcery/external.rb at 5fae86fcaf836e32d29ff456ce96bcb65dda6922 · Sorcery/sorcery
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
module Sorcery
module Controller
module Submodules
module External
module InstanceMethods
protected
# 一部省略
def sorcery_login_url(provider_name, args = {})
@provider = sorcery_get_provider provider_name
sorcery_fixup_callback_url @provider
return nil unless @provider.respond_to?(:login_url) && @provider.has_callback?
@provider.state = args[:state]
@provider.login_url(params, session)
end
# このメソッドをログイン時のコントローラで呼び出している
def login_at(provider_name, args = {})
redirect_to sorcery_login_url(provider_name, args)
end
end
end
end
end
end
|
つまり、login_at
は、最終的には@provider.login_url(params, session)
の返り値にリダイレクトしているだけ。
この@provider
はSorcery::Providers::Github
のインスタンスである。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
[1] pry(#<OauthsController>)> sorcery_get_provider 'github'
=> #<Sorcery::Providers::Github:0x00007fb8cda73978
@auth_path="/login/oauth/authorize",
@callback_url="http://127.0.0.1:3000/oauth/callback?provider=github",
@key="*********",
@original_callback_url="http://127.0.0.1:3000/oauth/callback?provider=github",
@scope="user:email",
@secret="********************",
@site="https://github.com/",
@state=nil,
@token_url="/login/oauth/access_token",
@user_info_mapping={:display_name=>"name", :email=>"email", :screen_name=>"login"},
@user_info_path="https://api.github.com/user">
|
Sorcery::Providers::Github
にlogin_urlが定義されている。これが認証時の遷移先URLになる。
sorcery/github.rb at 5fae86fcaf836e32d29ff456ce96bcb65dda6922 · Sorcery/sorcery
ちなみに、Github
モデルはSorcery::Providers::Base
を継承しており、ここにattributeとしてcallback_url
が定義されている。
sorcery/base.rb at 5fae86fcaf836e32d29ff456ce96bcb65dda6922 · Sorcery/sorcery
1
2
3
4
5
6
7
8
9
10
11
|
module Sorcery
module Providers
class Github < Base
include Protocols::Oauth2
def login_url(_params, _session)
authorize_url(authorize_url: auth_path)
end
end
end
end
|
また、Sorcery::Protocols::Oauth2
をincludeしているので、以下のメソッドが内包されているのと同義である。
sorcery/oauth2.rb at 6fdc703416b3ff8d05708b05d5a8228ab39032a5 · Sorcery/sorcery
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
module Sorcery
module Protocols
module Oauth2
def authorize_url(options = {})
client = build_client(options)
client.auth_code.authorize_url(
redirect_uri: @callback_url, # ここで、リダイレクトURLとしてmodelに定義されたcallback_urlを渡している
scope: @scope,
display: @display,
state: @state
)
end
end
end
end
|
つまり、@provider
であるGithub
インスタンスのcallback_url
にコールバックURLを代入すればいい。
この値は、デフォルトではconfig/initializers/sorcery.rb
で設定した値になる。
1
2
3
4
|
Rails.application.config.sorcery.configure do |config|
# 省略
config.github.callback_url = 'http://127.0.0.1:3000/oauth/callback?provider=github'
end
|
しかし、この@provider
は同一のオブジェクトなので、callback_urlが初期値にならず、以前のcallback_urlを保持してしまう。そのため、@provider.callback_url =
を設定しない場合は、前回の値のままになる。
なので、@provider.callback_url =
で動的にコールバックURLを変更する場合は、どのパターンであろうと設定するようにする。つまり、上記のデフォルトコールバックURLは機能しない。
どうして同じオブジェクトなの?
@provider = sorcery_get_provider provider_name
でproviderオブジェクトを取得しているが、このConfig.send(provider_name.to_sym)
はConfig.github
と同じ意味。
1
2
3
4
5
|
def sorcery_get_provider(provider_name)
return unless Config.external_providers.include?(provider_name.to_sym)
Config.send(provider_name.to_sym)
end
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
module Sorcery
module Controller
module Submodules
module External
def self.included(base)
base.send(:include, InstanceMethods)
Config.module_eval do
class << self
attr_reader :external_providers
attr_accessor :ca_file
def external_providers=(providers)
@external_providers = providers
providers.each do |name|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def self.#{name}
@#{name} ||= Sorcery::Providers.const_get('#{name}'.to_s.classify).new
end
RUBY
end
end
end
end
end
end
end
end
end
|
この
1
2
3
|
def self.#{name}
@#{name} ||= Sorcery::Providers.const_get('#{name}'.to_s.classify).new
end
|
は、つまり、
1
2
3
|
def self.github
@github ||= Sorcery::Providers.const_get('github'.to_s.classify).new
end
|
なので、既にインスタンスが生成されて代入されているので、新しいインスタンスは生成しない。
(この||
を消せば初期化問題は解決しそうだけど、影響の大きさが想像つかないのと、これ以上ハックするのってどうなんだ……)
注意
遷移先は、外部認証側のコールバックURL設定も必要。
GitHubの場合は↓
CALLBACK: http://example.com/path
GOOD: http://example.com/path
GOOD: http://example.com/path/subdir/other
BAD: http://example.com/bar
BAD: http://example.com/
BAD: http://example.com:8080/path
BAD: http://oauth.example.com:8080/path
BAD: http://example.org
Authorizing OAuth Apps - GitHub Docs
おわりに
login_url(_params, _session)
の_params
でコントローラに渡された値は取得できるので、それをauthorize_url
に渡してこねこねすれば、もっとスマートに行けそう。
Github
モデルに独自のauthorize_url
を定義してオーバーライドするようにするとか。