Log in as a user

Friday 15 May 2009

This s a question that pops up every once in a while in web site technology discussion forums: if I’m running a web site, what’s the best way to log in as a particular user so that I can see the site as they would see it?

The simplistic thing to do is to somehow get the user’s password, and just log in as them. But this is bad because you should never need to know your users’ passwords, and ideally, you don’t even have access to them.

The better solution is to interpret the classic username and password fields liberally. The username field is always interpreted as the desired username. The clever part comes with the password field: it can either be the user’s password, or it can be a combination of a super-user username and the super-user’s password. It’s the online equivalent of getting access to Joe’s stuff either by showing your identity card proving you are Joe, or by being a policeman and showing your badge.

To make it concrete, imagine authentication is implemented in a function called who_am_i that takes the username and password, and returns the authenticated username, or None. You have an is_password function that can tell you whether a username and password match, and an is_superuser function that can tell you whether a username is a super-user.

Here’s the classic username/password authentication:

def who_am_i(username, password):
    """Determine what user these credentials represent."""
    if is_password(username, password):
        return username
    return None

Here’s the extended version. An @-sign is the separator in the password field for the super-user’s name and password:

def who_am_i(username, password):
    """Determine what user these credentials represent."""
    if is_password(username, password):
        return username
    if '@' in password:
        super_name, super_pass = password.split('@', 1)
        if is_super(super_name):
            if is_password(super_name, super_pass):
                return username
    return None

I like this solution because it doesn’t require any extra UI, but gives super-users what they need: a way to log in any user they want, without knowing the user’s password. If you like, you can strengthen the super-user path by requiring white-listed IP addresses, or special privilege cookies, etc.

BTW, stackoverflow has more discussion on the same topic, including other (bad) ideas about how to do it (change the user’s password??)

Comments

[gravatar]
Les Hill 12:28 PM on 15 May 2009

The solution we have used in our web apps is to have the authN/authZ return the desired user as the effective user. We do this by having an admin feature to setup a masquerade, and then when we ask for the currently logged in user (current_user), we handle the masquerade:

      def current_user_with_effective_user
        if masked?
          current_user_without_effective_user.masquerade_as
        else
          current_user_without_effective_user
        end
      end
      alias_method_chain, :current_user, :effective_user

[gravatar]
Jani Hartikainen 12:27 AM on 16 May 2009

Seems like a decent way to do this without having separate UI. I do prefer giving admins some way of switching personalities, similar to "su" in *nix shell. As an added bonus, it won't limit the characters you can put into usernames/passwords - which is something I hate when it happens

btw was unable to post a comment in Opera... could be because I was using Opera 10 and not one of the stable releases, though.

[gravatar]
Ned Batchelder 5:34 AM on 16 May 2009

"it won't limit the characters": I'm not sure if you're saying my solution limits the password characters. Although it interprets @ specially, note that if a user wants to use an @ in their password, that's fine. Only if the password isn't the user's password is the @ interpreted specially.

[gravatar]
Stan Angeloff 9:27 AM on 16 May 2009

The solution you have outlined is really powerful. I am going to include it in my login managers from now on. What we've done until now is to have a special page available to superadmins where the entire organisational structure is presented in a tree view. Against each record we put a button Impersonate that launches a new window with the selected user's view of the system. This introduces many problems, takes time to put together and in most cases is an overkill for what is required.

[gravatar]
iain 12:52 AM on 18 May 2009

What you are doing is:
(1) If normal user - let me in
(2) If superuser - let me in

If you already have (1) you don't need to check (2). So I suggest:

elif '@' in password:
instead of:
if '@' in password:
Or if you want to check for superuser status first (and flick on a superuser bit), do (2) elif (1) instead of (1) elif (2).

[gravatar]
iain 1:02 AM on 18 May 2009

Just reread my comment, which is unnessecary becuse the return will end the function. Sorry ...

[gravatar]
petrilli 6:21 PM on 9 Aug 2009

For what it's worth, I posted a snippet for Django to do exactly this same thing. Might be useful.

http://www.djangosnippets.org/snippets/1590/

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
URLs auto-link and some tags are allowed: <a><b><i><p><br><pre>.