facebook/twitter/googleなどでログイン/ログアウトが出来るようになったものの、ログイン時にグループID的なやつを付けないと色々と問題が発生することがわかったので試した内容のメモ。
グループIDについてはDBに登録されていることが前提としている。
つまり登録されていないIDはエラー扱い。モデル名はGroupInformationとする。
1. クライアント側
こういう風にすればOKという情報しかなかった。
http://localhost:3000/auth/:provider?group_id=1
devise_token_authというよりはdeviseでのやり方だったので、正直怪しかったけど結局クライアント側としてはこれで問題はなかった。
2. サーバ側
以下のようにして対応する。(変更箇所のみ抜粋)
module Users
class OmniauthCallbacksController < DeviseTokenAuth::OmniauthCallbacksController
include Devise::Controllers::Rememberable
before_action :group_id_valid?, only: [:omniauth_success]
def redirect_callbacks
session[:group_id] = request.env['omniauth.params']['group_id']
super
end
def omniauth_success
logger.debug(session[:group_id])
# なんか処理
session.delete(:group_id)
super
end
# 省略
private
def group_id_valid?
GroupInformation.find_by_id(session[:group_id]).nil?
end
end
end
最初はredirect_callbacksでparams['group_id']を使えば良いじゃんと思って試したけど、facebookやgoogleは無理だった。
3. 付加したグループIDのチェック
ミドルウェアで/auth/:providerにリクエストがきたときに、グループIDが付いていない場合は弾いたほうが良いんじゃないかなーとはおもっているんだけど、正直どのタイミングが良いのかわからん。
変にredirect_callbacksでグループIDを確認して弾く(認証処理を中断する)と
facebookでは「アカウントの安全確認」が発動してアカウントが一時的に凍結されてしまうことがある。
実際params['group_id']でグループIDが取れるのか試したときに、例外で認証処理が中断された結果、facebook側に不審なアクティビティ(だっけ?)と思われてしまい、アカウントが数日凍結された。おのれfacebook
なのでミドルウェアで弾いてしまうか、provider側の認証処理が終わってから確認するかどっちかになるんじゃないのかと。
どっちがいいんだろ?
上記の例では認証成功後に確認するようにしたけど、もしミドルウェアでチェックするならこんな感じ?
require 'rack'
module Omniauth
class CustomMiddleware
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
request = ActionDispatch::Request.new(env)
if request.fullpath.include?('/auth') && !request.fullpath.include?('/callback')
if request.params.has_key?('group_id')
if group_id_valid?(request.params['group_id'])
status, headers, body = error_response(status, headers, no_registered_error)
end
else
status, headers, body = error_response(status, headers, no_query_error)
end
end
response = Rack::Response.new body, status, headers
response.finish
end
private
def error_response(status, headers, body)
content_length = body.inject(0) do |sum, content|
sum + content.bytesize
end
headers["Content-Length"] = content_length.to_s
headers.delete_if {|key, val| key == "Location" }
status = 400
[status, headers, body]
end
def no_query_error
["<html><body>Group ID is not found. </body></html>"]
end
def no_registered_error
["<html><body>Group ID is not registered. </body></html>"]
end
def group_id_valid?(group_id)
GroupInformation.find_by_id(group_id).nil?
end
end
end
group_idがついていなかったり、存在しないgroup_idが付加されているなら、400エラーを返すようにレスポンスを書き換える。そうでなければ何もしない(そのまま処理を続行)。
ActionDispatch::Requestや@app.call(env)を使わずにenvから自分で取り出してやったほうがいいのかなー。