Our app relies entirely on the
current_user helper method (found in our
ApplicationController) to retrieve the
User object associated with the currently logged-in user (if any). You guys are using the
devise gem for authentication, but you still have a
current_user helper method which is made available to
ApplicationController (and all subclasses).
current_user method looks something like this:
def current_user # do some magic to get the user id from the (encrypted) session cookie… user_id = get_user_id_from_session # load and return the associated User model (if applicable)… User.find user_id unless user_id.nil? end
We change this a bit by first adding some helper methods to our
ApplicationController (I'll go into implementation details later):
true_user- This method returns the logged-in
Userignoring impersonation. This does what the above
impersonated_user– This method returns the
Userrepresenting the user being impersonated. Returns
nilif no one is being impersonated.
impersonate_user!(user)– This method starts impersonating the given user.
stop_impersonating_user!– This method stops impersonation.
trueif, and only if, the logged-in user is currently impersonating another user.
We then change the current_user method from the pseudo-code above to something like:
def current_user @current_user ||= impersonated_user || true_user end
If a user is currently being impersonated,
current_user will return that
User (object). If not,
current_user will act how it normally does.
Take a look at the files below for some implementation details. For now, ignore the methods marked FEATURE X, these methods are used in the implementation of additional features which I'll describe a bit later.
You'll notice that we keep track of the impersonated user by writing the user ID to the session (which is the same way we keep track of the logged-in user). To make it all work, we added an action in our
Admin::UsersController that calls
impersonate_user! with the given user ID, and then redirects the user to the root URL (where they’ll start seeing the site as the impersonated user). A button in our Admin interface hits this action (with the chosen user id passed as a parameter), and voila, the impersonation begins.
The pieces I've outlined above should be all that you need to get it working. In our implementation, there are a couple additional pieces of code that we've added to achieve some additional functionality:
- Impersonation should be ignored in the Admin area of the site. This means an administrator can continue to perform tasks in our Admin console (as him/herself) while impersonating another user. We accomplish this with the update marked FEATURE 1 in the files below.
- If currently impersonating another user, logging out of the site should only log out the impersonated user, not the true user. Exception: If the user logs out of the site from within the Admin console, he/she will be logged out entirely. In other words, if I'm logged in as myself (an administrator), and then I start impersonating a regular user (John Doe), when I log out of the site, I'll only be logging out as John Doe, so I'll be able to continue browsing the site as myself. If I then log out again, I'll be logging out as myself, so I'll be completely logged out. We accomplish this with a modification that would probably not be totally analogous for you guys with devise, but I’m sure it’s doable. See the methods marked FEATURE 2.
We also made some changes to the UI to make sure that administrators don't lose track of the fact that they're impersonating another user. When impersonating another user, we style the user actions menu (always visible in the upper right corner of the page) to be bright red. When the user hovers the mouse over this menu while impersonating a user, a red box pops up showing the name and user ID of the impersonated user. While this feature certainly isn't necessary, our admins find it very helpful.