Lessons from a life of startups, coding, countryside, and kids
The ability to log in as one of your users is one of the highest value features you can develop to support your customers.
The ability to log in as one of your users is one of the most dangerous features you can develop to support your customers.
With that pithy introduction out of the way…
No, actually, let’s back up a minute because I’m not sure that you’ve fully appreciated what you’re about to do: you are creating a security hole in your app.
If your app is Helm’s Deep, then impersonating users is like adding a small unguarded culvert that bypasses the main fortifications. You should expect Orks… and add the appropriate defences.
Still not afraid? Oh, maybe you’ve heard of Facebook? Yeah, this feature you’re about to blithely implement resulted in 90 million compromised accounts yesterday.
So, are you afraid now? Good. You may continue.
There’s a lot of things to consider when implementing a feature like this and the technical details are possibly the least interesting. They also vary considerably between apps, frameworks, and languages.
Technically, logging in as another user is probably as simple as
session[:current_user] = user.id or something similar. Whatever. You probably know how this works.
Logging in as another user is not the hard part.
Here’s some more important things to consider:
This might not be required in every application but if you’re dealing with sensitive or financial data you might need to ask the user’s permission before viewing their account. I’ve seen this implemented by FreeAgent as a special code visible in the user’s settings which must be provided directly to the support staff
The user can also opt-in/opt-out off allowing support staff to access their account.
This is really an internal company process but you should be clear about who can and cannot impersonate a user, under what circumstances, and for what purposes. You probably want your support staff to impersonate users so they can fix/debug an ongoing issue. You probably don’t want your sales people impersonating users out of idle curiosity.
One feature I’ve previously built is some form of accountability. You might build an audit log in the database recording each time a member of the support staff impersonated a user. Personally, I think audit logs are great for analysing abuse after it’s occurred but do little to act as a deterrent. Instead, I think good behaviour can be enforce by announcing the impersonation publicly — posting to a Slack channel each time some one is impersonated is a simple method of ensuring accountability.
Now that your admin accounts are a backdoor to every user account, it’s time to take another look at their security.
First, I think it’s important to have separate
User models as the simplest way to avoid privilege escalation attacks
Next, we should ensure that it really is an admin impersonating the user. But don’t we just check that they’re logged-in as an admin?
Ha! Er… no. What happens if your support staff laptop is stolen? Or they’ve reused a password? You need another means of verifying it’s really an admin user. A sort of second password…a two-factor authentication if you will. 2FA. Top tip: just use https://www.twilio.com/authy to generate and confirm a confirmation code. It’s dead simple and will take a few hours at most.
This ensures that the logged-in admin account is being operated by the member of staff you think it is.
A fairly common problem occurs when you impersonate a user on Friday, and then on Monday you open the app and forget you’re logged into that user’s account. Hopefully you realise in time before you do anything too… permanent like send a newsletter out with the wrong account.
A simple solution is to expire the impersonation much quicker than normal session cookies. If your user sessions normally last 30 days, I’d reduce the session timeout for impersonations to something like 1 hour.
Even if you limit the duration, you’ll still want to display some indication that they’re impersonating another user.
In one app, I added a large/prominent ghost 👻 fixed in the left-hand corner which would end the session when clicked. It was a fun but important feature. A banner at the top works just as well
It’s only after you’ve built an impersonation feature that you discover all the unintended side-effects. Try to shortcut this process by considering where else you send your user information.
Some of my hard-won lessons include:
It’s slightly more complex to impersonate users when they’re on different subdomains or custom domains. The basic process isn’t too arduous though:
session[:current_user] = user.idyour app requires
So here’s the outline process for impersonating a user:
session[:current_user] = user.id. Or you might do the more complex multi-tenant dance with tokens and redirects.
session[:impersonating] = user.id