Last Updated: February 25, 2016
·
3.739K
· dennismonsewicz

Devise: Fast User Switching using a custom Authentication Strategy

Recently, my company needed a way to allow for users with a certain Access Control to log in (or impersonate) another user.

To be able to keep up with the original user, checking to see if the original user has access to the given user they would like to impersonate and signing in the user for impersonation, we came up with the following strategy for Warden.

# lib/sign_in_as.rb
require 'devise/strategies/base'

module SignInAs
 module RememberContributor
 extend ActiveSupport::Concern

 private

 def remember_contributor_id
 request.env['rack.session']['devise.remember_contributor_id']
 end

 def remember_contributor_id=(id)
 request.env['rack.session']['devise.remember_contributor_id'] = id
 end

 def remember_contributor_id?
 request.env['rack.session'] && request.env['rack.session']['devise.remember_contributor_id'].present?
 end

 def clear_remembered_contributor_id
 request.env['rack.session']['devise.remember_contributor_id'] = nil
 end
 end
end
# config/initializers/sign_in_as.rb

require 'devise/strategies/authenticatable'

module Devise
 module Strategies
 class SignInAs < Authenticatable

 include ::SignInAs::RememberContributor

 def valid?
 user = User.find_by_id(params[:id])

 if user.athlete?
 contributor_has_access? && (ability.can?(:create, user) || ability.can?(:update, user))
 else
 clear_remembered_contributor_id
 true
 end
 end

 def authenticate!
 resource = User.find_by_id params[:id]

 if resource
 success!(resource)
 else
 fail!("You do not have sufficient access to this account")
 end
 end

 private
 def contributor_has_access?
 contributor_user.school_admin? || contributor_user.athlete_contributor? || contributor_user.class.name.eql?("HighSchoolCoach")
 end

 def contributor_user
 User.find(remember_contributor_id)
 end

 def ability
 @ability ||= "::Abilities::#{contributor_user.class.name}Ability".constantize.new(contributor_user)
 end
 end
 end
end

Warden::Strategies.add(:sign_in_as, Devise::Strategies::SignInAs)
class SignInAsController < ApplicationController
 before_filter :authenticate_user!

 include SignInAs::RememberContributor

 def create
 # Let's remember the contributor ID for use in the Warden::Strategy
 self.remember_contributor_id = original_user.try(:id) || current_user.id
 # Sign out current user
 sign_out(current_user)

 # If original_user and original_user ID eql params[:id].to_i, log original_user back in
 if original_user && original_user.id == params[:id].to_i
 sign_in(:user, original_user)
 redirect_to user_root_path
 else
 # Else pass off request to custom Warden::Strategy
 handle_request
 end
 end

 private
 def handle_request
 # IF Warden autheticates using Devise::Strategies::SignInAs, redirect them to the correct path
 if env['warden'].authenticate(:sign_in_as)
 redirect_to user_root_path
 else
 # ELSE sign the contributor back into their account, and tell them they no have rights, they go home
 sign_in(:user, User.find(remember_contributor_id))
 redirect_to user_root_path, notice: "You do not have sufficient rights"
 end
 end
end