Reputation: 2900
I'm using Devise to authenticate User in my Rails 7 app and Pundit for authorization. I would like to extend the standard login flow to check if the user has met the 2FA requirements. 2FA is simple and delivered by an external microservice - the user at each login enters the SMS (text message) code he got on his phone.
In a nutshell new login flow should be:
session[:_2fa] == 'success'
session[:_2fa] == 'failed'
Because the user must go through the 2FA process every time he logs in, I've store such info inside the session params as session[:_2fa]
. Now to make all the authorization and redirect dance I want to have access to that session to allow or not. I'm aware of this topic but it's 7y old so maybe today there is some modern approach instead of creating fake model just to get access to the session?
Code below:
# application_controller.rb
class ApplicationController < ActionController::Base
before_action :authenticate_user!, :authorized_user
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def authorized_user
authorize :global, :_2fa_passed?
end
private
def user_not_authorized
flash[:alert] = t('errors.user_not_authorized')
redirect_to provide_code_path
end
end
# global_policy.rb
class GlobalPolicy < ApplicationPolicy
def _2fa_passed?
session[:_2fa] == 'success'
end
end
Upvotes: 1
Views: 330
Reputation: 2313
Pundit documents this in Additional Context section of the readme.
Instead of user instance you can pass pundit user context object which would hold user instance and extra session context like 2FA authentication status.
class UserContext
attr_reader :user, :second_factor_authenticated
def initialize(user, second_factor_authenticated)
@user = user
@second_factor_authenticated = second_factor_authenticated
end
end
class ApplicationController
include Pundit::Authorization
def pundit_user
second_factor_authenticated = session[:_2fa] == 'success'
UserContext.new(current_user, second_factor_authenticated)
end
end
Then in policy classes rename your user
attribute to context
and refer to user instance through context.user
and 2FA status through context.second_factor_authenticated
.
So to answer the question - seven years later it is still perfectly acceptable solution.
Given you're using Devise an alternative could be adding ephemeral attribute second_factor_authenticated
to user class and implementing your own .serialize_into_session
/ .serialize_from_session
to store it as part of user "vector" (by default it's only user id + auth salt).
Upvotes: 0