Recently, I created thoughtbot Foursquare lists to help out-of-town workshops students find hotels, coffee shops, and bars.

In the process, I noticed Foursquare has an “act as” feature:

It lets you, well, “act as” another user:

It caught my eye because I had recently implemented a similar feature for a client. I named it with slightly more aplomb: “masquerading.”

an authentication library

aplomb

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.