Deviseを使っていて意図しないタイミングで認証処理が実行されてしまったことがあったので、そのことについてまとめてみます。
class Users::SessionsController < Devise::SessionsController
# POST /resource/sign_in
def create
user = User.find_by(email: sign_in_params[:email])
if user.two_factor_enabled
return redirect_to users_two_factor_path
end
super
end
例えば、Devise::SessionsControllerのcreateアクションをオーバーライドし、2要素認証を行うユーザであれば、ログインせずに別の画面にリダイレクトさせたいとします。
上記のコードではtwo_factor_enabled
がtrue
の場合、認証処理をせずにリダイレクトしているため、リダイレクト後の画面ではログインしていない状態を期待していましたが、実際にはログイン済みの状態になっていました。
current_userの挙動
リダイレクト後の画面で意図せずログイン状態になってしまうのは、current_user
が呼ばれていたためです。
current_user
は、認証しているユーザを返すメソッドですが、それだけでなく、認証していなければ認証処理をしてユーザを返す実装になっています。
def current_#{mapping}
@current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end
先ほどのコードではcurrent_user
は呼び出されていないように見えますが、例えば、Logrageのようなライブラリを使用し、以下のようにユーザのIDを出力するようにしていると、アクションが処理された後、ログ出力時にcurrent_user
が呼ばれて、認証されてしまいます。
Rails.application.configure do
config.lograge.enabled = true
config.lograge.custom_payload do |controller|
{
host: controller.request.host,
user_id: controller.current_user.try(:id)
}
end
end
回避策1
リダイレクト先のアクションでsign_outメソッドを呼ぶようにする。
別アクションではパラメータ(emailやpassword)が渡ってこないため、ログ出力時にcurrent_user
が呼ばれても認証されません。
class Users::SessionsController < Devise::SessionsController
# POST /resource/sign_in
def create
user = User.find_by(email: sign_in_params[:email])
if user.two_factor_enabled
return redirect_to users_two_factor_path
end
super
end
def two_factor
sign_out(current_user)
end
回避策2
リクエストのパラメータによって認証するかどうかは、allow_params_authentication!
で制御しています。
そのため、このメソッドを呼び出さないようにすると、current_user
を呼び出しても認証されません。
class Devise::SessionsController < DeviseController
prepend_before_action :require_no_authentication, only: [:new, :create]
prepend_before_action :allow_params_authentication!, only: :create
Devise::SessionsControllerを継承すると上記のようにprepend_before_action
で呼ばれるようになっているので、このメソッドの呼び出しをスキップします。
class Users::SessionsController < Devise::SessionsController
skip_before_action :allow_params_authentication!, only: :create
# POST /resource/sign_in
def create
user = User.find_by(email: sign_in_params[:email])
if user.two_factor_enabled
return redirect_to users_two_factor_path
end
allow_params_authentication!
super
end
まとめ
ログ出力時に認証処理が実行されてしまった話を紹介しました。Devise::SessionsControllerのcreateアクションをオーバーライドし特定のユーザの認証を回避したいようなケースがあれば、current_user
による認証処理の実行に注意が必要かもしれません。