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?
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-inUser
ignoring impersonation. This does what the abovecurrent_user
implementation does.impersonated_user
– This method returns theUser
representing the user being impersonated. Returnsnil
if no one is being impersonated.impersonate_user!(user)
– This method starts impersonating the given user.stop_impersonating_user!
– This method stops impersonation.impersonating_user?
– Returnstrue
if, 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.
@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 ...