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).
Normally, the 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?
endWe 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-inUserignoring impersonation. This does what the abovecurrent_userimplementation does.impersonated_user– This method returns theUserrepresenting the user being impersonated. Returnsnilif no one is being impersonated.impersonate_user!(user)– This method starts impersonating the given user.stop_impersonating_user!– This method stops impersonation.impersonating_user?– Returnstrueif, 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
endIf 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.
@StephenRoos congrats! This is very interesting, I have been tried implement this, this gist will help me.
My API with Rails 5 uses devise_token_auth with ng-token-auth on frontend. My attempts for now have been frustrated.
I do not know how to propagate this to the frontend by updating the access token. More attempts I will make ...