Make Your Debugging Easier With 'Login As' Design Pattern

What is the "Login As" Design Pattern or Feature?

If one is authorized, they can impersonate (login as) another user without providing a password. I believe it should be in every application as it helps a lot in investigating production issues.

Why do we use this?

In many situations (mostly for debugging purposes), we want to log in as another user to see what that user is seeing or facing. This helps us understand the problem quickly and fix it. If it is working at your end, it would not be an application issue but a user environment or local settings issue.

Where do we need this?

Almost in every application wherever we've some authentication mechanism, we may need to investigate production issues.

Is this not dangerous?

Yes, it can be. Therefore careful development & proper logging of actions should be done. Sensitive Actions can be restricted when the user is in "Login As" mode. Normally read actions should be allowed but if write actions need to be allowed, details of the actual user should be saved too.

Assumption

We have a service that provides the following functions for user management.

  • AuthenticateUser(Login, Password): Takes Login & Password and returns User Object if a user is valid.
  • GetUser(ID): Takes User ID & returns User Object.

How do we do this in (Session-based) Web Applications?

Note. Although the sample code is showing C# syntax the concept is independent of language or development framework.

Normally we use Session in web applications to manage the state between multiple requests. We keep something in session when the user is authenticated and use that information later to check on authorized pages if the user has been authenticated or not. For example, a user is authenticated and we stored the following information. Here userObj is the User Object which we got from the AuthenticateUser Method.

var userObj = service.AuthenticateUser(Login, Password);
string login = userObj.Login; // e.g. bilal
bool canLoginAs = userObj.CanLoginAs; // e.g. true

// We'll add some logic here to handle when user uses Login As

Session["ID"] = userObj.ID; // e.g. 100
Session["Login"] = login; // e.g. bilal
Session["CanAsLogin"] = canLoginAs; // true

In the above code, we have stored information on whether a user can use the Login As feature or not. This permission came in User Object.

We can use Session ["CanAsLogin"] to decide if the current user is authorized to use the "Login As" feature or not. Based on this flag, we can enable the Login As Panel. The login Panel is like the Login screen but without a password and takes the User ID (You may change it according to your authentication system). Let's suppose we want to LoginAs with ID=150. We'll provide input and click on the "Login As" button. On the server side, we'll make a call to the GetUser() function and will get the User Object. Here is an updated version of the above code.

var userObj = null;
if (NormalLogin == true) {
    userObj = service.AuthenticateUser(Login, Password);
} else if (LoggedInAsFeature == true) //It means we came here from LoginAs Panel not Login screen.
{
    userObj = service.GetUser(user_provided_input); //e.g. 150
}
String login = userObj.Login; //e.g. shahzad
Boolean canLoginAs = userObj.CanLoginAs;
if (LoggedInAsFeature == true) //It means we came here from LoginAs Panel not Login screen.
{
    Session["IsLoginAs"] = true;
    Session["OldID"] = Session["ID"]; // 100 will be stored against OldID key
    login = login + "_" + Session["Login"]; // shahzad_bilal
    canLoginAs = false;
}
Session["ID"] = userObj.ID; //e.g. 150
Session["Login"] = login; //shahzad_bilal
Session["CanAsLogin"] = canLoginAs; //false

Note that we've introduced two new data members in session to track original user information. Also, on the front end, we'll show the "Login Back" option instead of "Login As". Now we are fully logged in with ID=150. We would be using Session["Login"] in our transactions to log who is creating of modifying a record. Anyone later can see from the logs who was an actual user & which was a "Login As" user.

Login Back Feature?

Here is a sample code to explain all the above cases. It is not optimized or final code. This is just to give you an idea of the concept. Change or optimize according to your needs.

var userObj = null;
if (NormalLogin == true) {
    userObj = service.AuthenticateUser(Login, Password);
} else if (LoggedInAsFeature == true) {
    userObj = service.GetUser(user_provided_input); // e.g. 150
} else if (LoggedBackAsFeature == true) {
    int id = Session["OldID"]; // 100
    userObj = service.GetUser(id);
}
string login = userObj.Login;
bool canLoginAs = userObj.CanLoginAs;
if (LoggedInAsFeature == true) // It means we came here from LoginAs Panel not Login screen.
{
    Session["IsLoginAs"] = true;
    Session["OldID"] = Session["ID"]; // 100 will be stored against OldID key
    login = login + "_" + Session["Login"]; // shahzad_bilal
    canLoginAs = false;
} else if (LoggedBackAsFeature == true) // It means we came here from Logging Back button.
{
    Session["IsLoginAs"] = false;
    Session["OldID"] = null;
}
Session["ID"] = userObj.ID; // e.g. 100
Session["Login"] = login; // bilal
Session["CanAsLogin"] = canLoginAs; // true

Summary

"Login As" allows you to do this investigation on your machine. Very small changes are required in the application but these should be done carefully and proper logging should be maintained.