OmniAuth(OAuth2)処理をキックしたときのパラメータを認証用リクエストにも渡したい
はじめに
RailsアプリにOAuth2認証を導入する場合に利用できるOmniAuthがあります。
TwitterやGitHubなどの外部認証用にすでにいくつもStrategyが用意されています。
リストにない場合は自作することになりますが、認証サービス用のStrategyを作成時にはまった点を紹介します。
特にOmniAuth処理をキックされたときの動的なパラメータを扱う点は、検索しても情報が見つけられなかったので苦労しました。
参考になれば幸いです。
ここで取り上げるポイント
- OAuth用のURLを変更する
- callback_urlの注意点(デフォルトのままだとエラーになってしまうパターン)
- OmniAuth処理をキックされたときのパラメータを認証時のパラメータとして渡す
- OmniAuth処理をキックされたときのパラメータをapi呼び出し結果に格納する
OAuth2の処理の流れはこちらをご覧ください。(とてもわかりやすかったです)
「OmniAuth OAuth2 を使って OAuth2 のストラテジーを作るときに知っていると幸せになれるかもしれないこと」
OmniAuthでOAuth2用のカスタムStrategyを作成
前提
今回は架空のhogeというOAuth2認証サービス用のstrategyを作成します。
API側
例 | |
---|---|
サイト | https://hoge.xxx.xxx |
認可エンドポイント | https://hoge.xxx.xxx/oauth/authorize |
認可時に渡すカスタムパラメータ | group_id=9876543210 |
api利用用に登録したRedirect URL | https://myapp.localhost/sessions/callback |
呼び出すapi | https://hoge.xxx.xxx/api/user_information |
apiの戻り値 | json形式※1 |
{
"user_id" : "012345",
"first_name" : "Firstname",
"last_name" : "Lastname",
"email" : "test@localhost"
}
OAuthを利用するWebアプリ側
例 | |
---|---|
サイト | https://myapp.localhost |
OAuth用リクエストパス | https://myapp.localhost/sessions/auth |
OAuth用コールバックパス | https://myapp.localhost/sessions/callback |
OAuth認証時に呼び出すリクエストパスと、OAuth認証結果を受け取るコールバックパスはデフォルトのパスから変更しています。
変更前(デフォルト) | 変更後 |
---|---|
https://myapp.localhost/auth/hoge |
https://myapp.localhost/sessions/auth |
https://myapp.localhost/auth/hoge/callback |
https://myapp.localhost/sessions/callback |
1. カスタムStrategyクラスを作成する
lib/omniauth/strategies/hoge.rb
を作成します。
https://github.com/intridea/omniauth-oauth2 などをひな形にして次のようなファイルを作成します。
require 'omniauth-oauth2'
module OmniAuth
module Strategies
class Hoge < OmniAuth::Strategies::OAuth2
# Give your strategy a name.
option :name, "hoge"
# This is where you pass the options you would pass when
# initializing your consumer from the OAuth gem.
option :client_options, { :site => 'https://myapp.localhost' }
# These are called after authentication has succeeded. If
# possible, you should try to set the UID without making
# additional calls (if the user id is returned with the token
# or as a URI parameter). This may not be possible with all
# providers.
uid{ raw_info['user_id'] }
info do
{
# JSON結果を加工しておける
user_id: raw_info['user_id'],
first_name: raw_info['first_name'],
last_name: raw_info['last_name'],
email: raw_info['email'],
# リクエストパラメータも取得できる
group_id: request.env['omniauth.params']['group_id']
}
end
extra do
{
'raw_info' => raw_info
}
end
def raw_info
@raw_info ||= JSON.load(access_token.get('/api/user_information').body)
end
# リクエストにカスタムパラメータを追加
def request_phase
_authorize_params = { :redirect_uri => callback_url }.merge(authorize_params)
_authorize_params.merge!({ group_id: request.env['omniauth.params']['group_id'] })
redirect client.auth_code.authorize_url(_authorize_params)
end
# リダイレクトURLを固定(デフォルトではクエリパラメータが付加されるため)
def callback_url
full_host + script_name + callback_path
end
end
end
end
以下、それぞれの説明です。
1.1 callback_urlの注意点
def callback_url
full_host + script_name + callback_path
end
ここでcallback_urlをオーバーライドしていますが、理由があります。
callback_urlはデフォルトでクエリパラメータが付加されるため、OmniAuth2::Client#request
でエラーになる可能性があります。(登録されているアプリと違うと判断されるため)
def callback_url
full_host + script_name + callback_path + query_string
end
Hoge#callback_urlでquery_stringが付加されない形に上書きしています。
1.2 OAuth認証時に動的にベンダー用カスタムパラメータを渡す
def request_phase
_authorize_params = {:redirect_uri => callback_url}.merge(authorize_params)
_authorize_params.merge!({ group_id: request.env['omniauth.params']['group_id'] })
redirect client.auth_code.authorize_url(_authorize_params)
end
https://hoge.xxx.xxx/oauth/authorize?group_id=9876543210
のようにOAuth認証にパラメータを追加したい場合の対応です。
固定値を渡す場合は、
:
option :authorize_params, { group_id: '9876543210' }
:
のように書いておけば良いのですが、動的に値を変えたい場合はどうすれば良いのでしょう?
このパラメータ(group_id)を、OAuth認証をキックするときに受け取るようにします。
https://myapp.localhost/sessions/auth?group_id=9876543210
OmniAuthでは、/sessions/auth
呼び出し時のパラメータが
request.env['omniauth.params']
に格納されています。
今回の例では、request.env['omniauth.params']['group_id']
となります。
このパラメータをOAuthリクエストパラメータに追加すればいいわけです。
パラメータを追加すべき処理は
OmniAuth::Strategies::OAuth2#request_phase
になります。
def request_phase
redirect client.auth_code.authorize_url({:redirect_uri => callback_url}.merge(authorize_params))
end
Hoge#request_phaseでパラメータを追加する形で上書きします。
この変更により
https://hoge.xxx.xxx/oauth/authorize?group_id=9876543210
のようにパラメータが渡されます。
1.3 api呼び出し結果を取得する
def raw_info
@raw_info ||= JSON.load(access_token.get('/api/user_information').body)
end
認可されたaccess_tokenを使ってapiを呼び出します。
今回は結果がjson形式で受け取れることを想定しているので、parseして@raw_infoに格納します。
1.4 結果をHashにセットする
uid{ raw_info['user_id'] }
info do
{
# JSON結果を加工しておける
user_id: raw_info['user_id'],
first_name: raw_info['first_name'],
last_name: raw_info['last_name'],
email: raw_info['email'],
# リクエストパラメータも取得できる
group_id: request.env['omniauth.params']['group_id']
}
end
extra do
{
'raw_info' => raw_info
}
end
OmniAuthによるapi呼び出し結果(OmniAuth::AuthHash)はrequest.env['omniauth.auth']
に格納されます。
次のように結果にアクセスできるので、その値をそれぞれ設定しています。
request.env['omniauth.auth'].uid
request.env['omniauth.auth'].info
request.env['omniauth.auth'].extra
request.env['omniauth.auth'].credentials # tokenに関する情報(今回上書きしてません)
このHashにはOmniAuth呼び出し時のパラメータも設定できます。
group_id: request.env['omniauth.params']['group_id']
2. HogeクラスをOmniAuthのProviderに設定する
OmniAuthがHogeを利用できるように config/initializers/omniauth.rb
を作成します。
require 'omniauth/strategies/hoge'
Rails.application.config.middleware.use OmniAuth::Builder do
provider :hoge, ENV['KEY'], ENV['SECRET'],
:request_path => '/sessions/auth', :callback_path => '/sessions/callback'
end
2.1 request_pathとcallback_pathを変更する
provider :hoge, ENV['KEY'], ENV['SECRET'],
:request_path => '/sessions/auth', :callback_path => '/sessions/callback'
namespaceを使っている場合など、認証用のパスと戻り値を受け取るパスを変更したい場合があります。
今回の設定では次のようにパスを変更しています。
対象 | 変更前(デフォルト) | 変更後 |
---|---|---|
requrest_path | /auth/hoge |
/sessions/auth |
callback_path | /auth/hoge/callback |
/sessions/callback |
3. 認証結果を元にユーザーを作成する
今回はSessionsController#create
で認証結果を受け取ります。
:
def create
user = User.find_or_create_from_auth_hash(auth_hash)
:
end
private
def auth_hash
request.env['omniauth.auth'].info
end
:
先程説明したように、apiの呼び出し結果はrequest.env['omniauth.auth']
で取得できます。
必要な情報はinfoで取得できるようにしてあるので、request.env['omniauth.auth'].info
の値を利用してUserを作成します。
:
def self.find_or_create_from_auth_hash(auth_information)
user = User.find_or_initialize_by(user_id: auth_information[:user_id])
user.email = auth_information[:email]
:
user.save!
user
end
:
最後に
OAuth2について詳しく知りたい場合は、
- OmniAuth::Strategy
- OmniAuth::Strategies::OAuth2
のコードを読むと良いかと思います。(いろいろ見えてきます)