Make Your Debugging Easier With 'Login As' Design Pattern

Normally, we want to debug an application on a user's machine by ourselves whenever a user reports some problem. We may ask the user to share his/her screen on a call etc. But what if we could log in with their credentials but we don't have the password. "Login As" allows you to do this investigation on your machine. We'll see small code snippets to understand the changes required for this.

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 login as another user to see what that user is seeing or facing. This helps us in understanding the problem quickly and fixing it. If it is working at your end, it would not be an application issue but user environment or local settings issues.
 

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, detail of actual user should be saved too.
 

Assumption

 
We have a service which provides the following functions for user management. 
  • AuthenticateUser(Login,Password): Takes Login & Password and returns User Object is 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 but the concept is independent of language or development framework.
 
Normally we use Session in web applications to manage 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 User Object which we got from AuthenticateUser Method
  1. var userObj = service.AuthenticateUser(Login,Password);    
  2. String login = userObj.Login; //e.g. bilal        
  3. Boolean canLoginAs = userObj.CanLoginAs;  //e.g. true    
  4.         
  5. //We'll add some logic here to handle when user uses Login As         
  6.           
  7. Session["ID"] = userObj.ID; //e.g. 100              
  8. Session["Login"] = login; //e.g. bilal        
  9. Session["CanAsLogin"] = canLoginAs; //true    
In the above code, we have stored information whether a user can use 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 "Login As" feature or not. Based on this flag, we can enable Login As Panel. Login Panel is like Login screen but without password and takes 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 "Login As" button. On the server side, we'll make a call to GetUser() function and will get User Object. Here is an updated version of the above code.
  1. var userObj = null;  
  2. if (NormalLogin == true) {  
  3.     userObj = service.AuthenticateUser(Login, Password);  
  4. else if (LoggedInAsFeature == true//It means we came here from LoginAs Panel not Login screen.  
  5. {  
  6.     userObj = service.GetUser(user_provided_input); //e.g. 150          
  7. }  
  8. String login = userObj.Login; //e.g. shahzad      
  9. Boolean canLoginAs = userObj.CanLoginAs;  
  10. if (LoggedInAsFeature == true//It means we came here from LoginAs Panel not Login screen.    
  11. {  
  12.     Session["IsLoginAs"] = true;  
  13.     Session["OldID"] = Session["ID"]; // 100 will be stored against OldID key      
  14.     login = login + "_" + Session["Login"]; // shahzad_bilal      
  15.     canLoginAs = false;  
  16. }  
  17. Session["ID"] = userObj.ID; //e.g. 150            
  18. Session["Login"] = login; //shahzad_bilal      
  19. 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 "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 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 need.
  1. var userObj = null;  
  2. if (NormalLogin == true) {  
  3.     userObj = service.AuthenticateUser(Login, Password);  
  4. else if (LoggedInAsFeature == true) {  
  5.     userObj = service.GetUser(user_provided_input); //e.g. 150        
  6. else if (LoggedBackAsFeature == true) {  
  7.     int id = Session["OldID"]; // 100    
  8.     userObj = service.GetUser(id);  
  9. }  
  10. String login = userObj.Login;  
  11. Boolean canLoginAs = userObj.CanLoginAs;  
  12. if (LoggedInAsFeature == true//It means we came here from LoginAs Panel not Login screen.        
  13. {  
  14.     Session["IsLoginAs"] = true;  
  15.     Session["OldID"] = Session["ID"]; // 100 will be stored against OldID key          
  16.     login = login + "_" + Session["Login"]; // shahzad_bilal          
  17.     canLoginAs = false;  
  18. else if (LoggedBackAsFeature == true//It means we came here from Logging Back button.        
  19. {  
  20.     Session["IsLoginAs"] = false;  
  21.     Session["OldID"] = null;  
  22. }  
  23. Session["ID"] = userObj.ID; //e.g. 100                
  24. Session["Login"] = login; //bilal          
  25. 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.