Skip to main content

20 Devise Masquerade as another User

Episode 170 · January 31, 2017

Add masquerading (the ability to impersonate or login as another user) to your development or admin environments using the devise_masquerade gem

Authentication


Resources

<% if user_masquerade? %>
  <div class="alert alert-warning text-center" style="margin-bottom: 0px">
    You're logged in as <%= current_user.name %>.
    <%= link_to back_masquerade_path(current_user) do %>Logout <%= fa_icon "times" %><% end %>
  </div>
<% end %>

Transcripts

What's up guys, this episode we're going to talk about Devise Masquerading and how to add this gem so that you can log in as other users to make doing support, and finding and reproducing bugs a little bit easier. This is really useful in development, but it can also be very useful in production if you have the need to do some support and maybe log in to someone's account and try to do something and reproduce a bug or maybe just help them with their account, so we're going to talk about the devise masquerade gem, and then I'd like to do a follow up where we talk about how this is actually implemented behind the scenes, because it's actually rather interesting and not as complicated as you might think.

This gem is an extension for devise, so you're going to have to have devise installed in order to use it. It basically just hooks in, and you have a few methods that you can use, so you have devise :masqueradable and in your application controller, you'd put in a before_filter :masquerade_user!, and then you have your user_masquerade? methods and the ability for you to undo the masquerade afterwards. So this is pretty straight forwards, if you're used to devise, all of these methods are kind of familiar you use similar ones for current user and authenticate user, and in this case we're just doing masquerading instead.

We'll need some sort of an admin area in order to go use this, so we'll go set that up, and then we will take a look at this.

The site I've got here is an open source project that I've got, this is really straightforward, it's a link sharing app, you drop in a link and it will parse out the Open Graph tags and then it will show up on the site, and then people can vote on it if they're interested, and one of the things that I would like to do to the admin area is to add the ability for me to login as user, so I want to be able to click on a different user and click on a button up here to masquerade as that user so that I can login as them and test things out in development.

With that said, this will save me a lot of time in development so we can go add this gem in, and I'm going to pull the gem from the latest version on rubygems, so we will have that, we can then drop this into our Gemfile, and go to our console, and run the Rails server after running bundler, and once that has finished and restarted the Rails app, we can go add this

= link_to "Login As", masquerade_path(user)

into our Admin views. My admin is already scoped to admin's only, of course, and that's going to be important for this because you don't want avarage users to be able to log into someone else's account so keep that in mind. Make sure that if you implement this feature, it's either only available in development, or only available for admins, and it's probably a good idea to write tests for that to make sure that no non-admins can access it.

With that, we can then open up our app/views/admin/users/show.html.erb (create the show view if you don't have it), and in the header action section, this is where we can add a <%= link_to "Login As", masquerade_path(page.resource), class: "button" %>, and we can go back to the README and look at the other changes that we need to do, so for example, we need to go to the user model, and make it masqueradable

models/user.rb
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :masqueradable

application_controller.rb

    before_action :masquerade_user!

There is a couple helpers we can use to check to see if the user is masquerading, and to also reverse the masquerade, but let's see if we get the link in the admin area,

Small bug corrected on the link_to line, transcript already correct to save you some annoyance

If we open a new tab, you can see that I'm logged in as myself, Chris O, and if I login as this user, we are now logged in as test user, and refreshing our other tab we see that we're logged in as the test user, so that means it successfully masqueraded us as that user.

What it's doing behind the scenes is actually taking the user_id that you have, and you're currently logged in as, and it's moving that to a different place called "masquerade user ID's" so it knows who is doing the masquerading so you can undo it and go back to that user account, and since it moves that, and then it replaces the current user ID with the masqueraded user, so whoever you choose to login as, it makes that the current user id, and it checks for the presence of the masquerade user and that way, you can add a bar at the top saying "You are logged in as this user", which we're going to do right now, and then we can put a link in there, and it will check in both cases to see if you have masquerade id in your cookies, and if it does, it will display that header so that you can go and remove the current user, and take the masquerade user and put them back as the current user.

It's pretty straightforward what it's doing but they take care of everything for you and just give you a couple helpers and a couple links and voilà, you can build this feature into your app in like five minutes, it's really cool. What we need to do then is to go into our application.html.erb and at the top of the body, we want to put in our own nav, and this can be a new nav that is to contain our masqueraded user but we only show this nav stuff if the user is currently masquerading, so let's take a look at the helpers, they have user_masquerade, and we can wrap this with that

views/layouts/application.html.erb

<% if user_masquerade? %>
    <nav>
        <%= link_to "Reverse masquerade", back_masquerade_path(current_user) %>
    </nav>
<% end %>

We get an un-styled nav bar here at the top, that says "Reverse masquerade", and if we click that, we get back to "Chris O" as the user, so we go back to my own account and we're automatically set up, so this is really really seamless for us to be able to go do that. Now if we go back to the admin, you will notice that when we login as test_user again, this time if we try to ask for the admin access, we don't get any access to it, there's no route that matches, so we're truly logged in as the other user and the only way for us to know if we're logged in from a different account, is they save that cookie in the session, so we know that masqueraded user. So you could do some extra stuff if you wanted to, to set up the admin area so that it would use that masqueraded user account, but I wouldn't really recommend that because this is going to keep that really straightforward. No matter who you are logged into, you can only access the things that they have permissions for, and that's probably the best way to go about this. So we can paste in a little bit of CSS to clean up this navbar and make it look pretty, but as far as that goes, we just have to add in a link, and another link with a little wrapper around it to display this nav bar here, and we have a fully functioning masquerading feature in our app.

Some magic happens off camera

And here is the styled version of it! I made a quick little change to this, I added an alert style from bootstrap three and changed it to an alert warning, and that makes it all pretty, so at the top of the page I can simply click the "Logout" button, and I'm back to my current user account, and anytime we see that bar above the navbar we know that we are logged in and masquerading as a different user.

So that's as simple as that feature is, if you want to see the html that I wrote for that, I'll put that in the notes below and that is all you have to do to make devise_masquerade work.

To follow up on this episode and another one, I would like to build this from scratch so that you can see how this works behind the scenes but as you might have noticed, this is a devise specific solution, which works really well, but what if you rolled your own authentication or you're using something else like clearance. How do you go about building a generic solution for this that does the same thing just may not provide you the wonderful integration to devise as this does. So we'll talk about that in another episode and follow up this by building it from scratch. 'Till then, I will talk to you later, peace v

Transcript written by Miguel

Discussion