Ever wondered how to login as another user within Devise?
Recently we had a feature request that would provide Admins with the ability
to sign is as another user. You could imagine live demonstrations or even production
support calls where you would like to be signed in as another user, yet not
have to ask for or decrypt their current password. After stewing for a bit, we found a
pretty nice solution with Devise.
Here’s the Cucumber feature…
Feature: Sign in as another user As an admin I want to sign in as another user Scenario: Admin can sign in as another user Given I am logged in as an admin user And a user: "bob" exists with email: "firstname.lastname@example.org", password: "password", ... When I go to the admin users page And I follow the "Sign in as" link for user: "bob" Then I should see "Welcome Bob"
The trick was to store the admin info within the rack session.
We decided on using a Rails 3 concern to keep our actual strategy clean.
require 'devise/strategies/base' module SignInAs module Concerns module RememberAdmin extend ActiveSupport::Concern private def remember_admin_id request.env['rack.session']['devise.remember_admin_user_id'] end def remember_admin_id=(id) request.env['rack.session']['devise.remember_admin_user_id'] = id end def remember_admin_id? request.env['rack.session'] && request.env['rack.session']['devise.remember_admin_user_id'].present? end def clear_remembered_admin_id request.env['rack.session']['devise.remember_admin_user_id'] = nil end end end end
The above really makes writing the Devise strategy fairly easy.
require 'devise/strategies/base' module SignInAs module Devise module Strategies class FromAdmin < ::Devise::Strategies::Base include SignInAs::Concerns::RememberAdmin def valid? remember_admin_id? end def authenticate! resource = User.find(remember_admin_id) if resource clear_remembered_admin_id success!(resource) else pass end end end end end end Warden::Strategies.add(:sign_in_as, SignInAs::Devise::Strategies::FromAdmin)
Then we just wire in our new strategy. Last line above.
Lastly, here’s our sign-in-as controller which sets the Devise session variable.
class SignInAsController < ApplicationController include SignInAs::Concerns::RememberAdmin layout 'admin/application' def create if can?(:manage, :users) self.remember_admin_id = current_user.id sign_in :user, User.find(params[:user_id]) end redirect_to '/admin' end end
That’s it, pretty neat and might make for a nice Gem or Devise addition.