How To Masquerade As Another User To See How They Use Your App
Recently,."
Ingredients
- an authentication library
- aplomb
Recipe
Here's what we wanted:
Given a user exists with email "bobby@example.com" and name "Bobby Tables" And an admin with email "admin@example.com" When I sign in as "admin@example.com" Then I should see "Bobby Tables" When I follow "Masquerade" within the "bobby@example.com" row And I should see "Now masquerading as Bobby Tables" And I should see "Hi Bobby" within the navigation When I follow "Stop Masquerading" Then I should be on the admin page
The context is that I'm an admin. A user is on the phone with me right now with support questions. I quickly find their account and see the app through their eyes.
In
app/views/admin/users/index.html.erb:
<% @users.each do |user| %> ... <%= link_to 'Masquerade', new_user_masquerade_path(user) %> <% end %>
In
config/routes.rb:
resources :users, only: [:edit, :update] do resources :masquerades, only: [:new] end
Nothing crazy so far.
In
app/controllers/masquerades_controller.rb:
class MasqueradesController < ApplicationController before_filter :authorize, :authorize_admin def new session[:admin_id] = current_user.id user = User.find(params[:user_id]) sign_in(user) redirect_to home_path, notice: "Now masquerading as #{user.name}" end def destroy user = User.find(session[:admin_id]) sign_in :user, user session[:admin_id] = nil redirect_to admin_users_path, notice: "Stopped masquerading" end end
The masquerading controller actions are restricted to admins by before filters provided by the authentication library and the developer.
The
create action switches the user ids and signs in as the user. Put your
mask on because we're masquerading.
When the customer support session is over, we'll want to return to the admin
views. In
app/views/shared/_navigation.html.erb:
<% if masquerading? %> <%= link_to "Stop Masquerading", "#" %> <% end %>
In
application_controller.rb:
def masquerading? session[:admin_id].present? end helper_method :masquerading?
We define that in a controller, then expose it as a helper method to the views,
so that we can alter the
authorize_admin method that is used as a
before_filter:
In
application_controller.rb:
def authorize_admin current_user.admin? || masquerading? end
That way, the
MasqueradesController stays protected, even when you're signed
in as a non-admin user during a masquerade.
We aren't using this technique on our own products right now. One concern is that on many apps, the customer's view could reveal sensitive data. Airbrake, for example, would require at minimum asking the person "may I act as your account?"
Heroku's ZenDesk support form does something similar: "May we access your application code? Check here to allow us to clone and/or inspect your code for debugging purposes."
However, I like the idea of providing better support by viewing the app as our customers do.