Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Devise::SessionsControllerのcreateアクションで認証を回避する際の注意点

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_enabledtrueの場合、認証処理をせずにリダイレクトしているため、リダイレクト後の画面ではログインしていない状態を期待していましたが、実際にはログイン済みの状態になっていました。

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による認証処理の実行に注意が必要かもしれません。



CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。