Click here to Skip to main content
Click here to Skip to main content

ASP.NET - Forms authentication user impersonation

, 12 Nov 2009
Rate this:
Please Sign up or sign in to vote.
An ASP.NET class and accompanying control for providing application support users with a 'login as user ...' function the right way.
A screenshot of ASP.NET forms authenticated user impersonation in action: The user 'Bob' is currently impersonating the user 'Alice'.

Introduction

Part of building a complete web application involves building a decent administration and support interface. A very important part of a good support interface is the ability of support users to login as an arbitrary client user. At the company I work for, they have several people sitting next to their phone at the office using this functionality every day. The company offers some kind of software as a service, their (trusted) support employees handle support calls from client users who have trouble getting stuff done in the application. If the issue isn't resolved directly, the support users use the functionality described in this article to log in as the troubled user 'and be able to see what the actual user sees'. The support users then either talk the client through the procedure for whatever they want done, or do it themselves directly. Half of the time, the support users at the company I work for even get called and just asked to do something for the client in the client's account.

For such operations to run smooth, the support users need to be able to jump from the administration and support interface directly into the client accounts. We can't go about resetting password and stuff; when they get a client on the phone, they just should be able to do what they have to do as quickly and efficient as possible.

While logging an ASP.NET user in as another user is quite easy; in fact, a call to FormsAuthentication.SetAuthCookie() will do the job; I, however, needed a way to really streamline this kind of functionality. I found it to be quite desirable for the support user to be able to jump right back to his or her support user account after impersonating the other user, without the hassle of having to really login as the support user again. Since I was unable to find any existing solutions, I developed the UserImpersonation class and the LoginUserImpersonation control presented in this article to tackle exactly that problem.

In this article, I will first briefly describe the requirements for using the UserImpersonation class and how to use it. After that, I will go into more detail on how I implemented user impersonation, and the motivation behind the decisions I made during the development.

Note: This article is not about what is usually referred to as 'Windows user impersonation'. This article only deals with ASP.NET forms authenticated users.

Using the UserImpersonation class

Requirements

In order to be able to make use of the UserImpersonation class, the following conditions and requirements apply:

  • One must make use of cookie based FormsAuthentication in order to be able to make use of the UserImpersonation class.
  • Cookie encryption is highly recommended while making use of the UserImpersonation class.
  • Only tested on .NET 2.0. The UserImpersonation class should thus work on .NET 2.0, 3.0, and 3.5.
  • You, at the moment, do not make use of the FormsAuthenticationTicket.UserData property in your ASP.NET application/website.

Reference

Using the UserImpersonation class is petty straightforward. The UserImpersonation is located in the System.Web.Security namespace, and has the following members:

  • public static void ImpersonateUser(string userName);
  • public static void ImpersonateUser(string userName, string returnUrl);
  • public static void Deimpersonate();
  • private static string Serialize(string userName, string returnUrl);
  • private static bool Deserialize(string data, out string userName, out string returnUrl);
  • public static string PrevUserName { get; }
  • public static bool IsImpersonating { get; }

Quick start

In order to start using the user impersonation functionality right away in your web application, follow the steps below:

  1. Download the FormsAuthenticationUserImpersonation precompiled binary into the /Bin folder of your ASP.NET application/website. This operation is equal to 'adding a reference' to the FormsAuthenticationUserImpersonation precompiled binary for your ASP.NET application/website.
  2. Register the FormsAuthenticationUserImpersonation assembly on the page you wish to use the LoginUserImpersonation control. This will likely be your master page. Registering an assembly is done using the <%@ Page %> directive as follows:
  3. <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="MasterPage.aspx.cs" Inherits="_Default" 
        Title="Your masterpage" %>
    
    <%@ Register Assembly="FormsAuthenticationUserImpersonation" 
      Namespace="System.Web.UI.WebControls" TagPrefix="asp" %>
  4. Add the LoginUserImpersonation control at the desired location on your ASP.NET page. Typically, you'll want to place this next to the login/logout button. The LoginUserImpersonation control will be rendered as a link button with the text 'Return to {UserName}':
  5. <asp:LoginUserImpersonation ID="LoginUserImpersonation1" runat="server" />
  6. Add the following code to your code where you want to actually start the user impersonation, where strUserName contains the name of the user you want to be logged in as. It is advised to redirect the user right after user impersonation is started, in order to make sure the changes take effect:
  7. UserImpersonation.ImpersonateUser(strUserName);
    Response.Redirect("~/YourPage.aspx");

Included example and source code

The included example website uses a rudimentary read-only MembershipProvider providing two users: Bob and Alice. For both of the users, the password is test.

The example website features a home page with a button allowing the logged in user to impersonate Alice, a login page, and a page only accessible by Alice.

The UserImpersonation class and LoginUserImpersonation control were written in C#. The source code is documented, and should be fairly easy to understand for any programmer at least being lightly acquainted with ASP.NET and C#.

Development

I will now discuss some interesting points I came across while developing this solution.

Design goals

  • Keep the user impersonation logic as close as possible to the existing user authentication logic.
  • Allow the user to easily return to his or her original account after impersonating another user without the hassle of having to login onto his or her own account again.

Keeping track of the original user

The Problem

As I already mentioned in the introduction, having a user to login as another user is actually quite simple. Just logging in the support user as the troubled client user will, however, result in the support user having to login into his or her account after he or she wants to end impersonating the other user. While this is no big deal for some dude administrating some small web application once a month, this will get really annoying for people working at the office helping clients all day using the application I am developing at the moment.

In short, I needed some way to log an user in as another user while at the same time keep track of the user he or she was previously fully authenticated for. This all needed to be done in a secure manner, because I wanted to give the user the ability to return to his or her original user account with a single mouse click, no re-entering passwords involved.

Authentication cookies

One secure and obvious way to handle this problem would be to keep track of the previous user by storing this information is the Session property bag. This, however, would require sessions to be enabled on every single page, or an HTTP handler reachable by authenticated users, and would require extra tracking code because the session does not necessarily expire when the user authentication cookie expires. I, therefore, decided it was not optimal to store the previous user information in the session.

The most logical place to store this information would be the authentication cookie itself, wouldn't it? It's securely encoded, and all information expires when the cookie expires (duh). The authentication cookie, however, is a little less accessible than the Session property bag. After some sniffing around on MSDN, I noted the FormsAuthenticationTicket class, which in a way represents the decoded version of the authentication cookie actually exposed as a property which allows one to include custom data with the cookie: FormsAuthenticationTicket.UserData.

Piggyback on the authentication cookie

When stating user impersonation, I use the following code to create a new authentication ticket for the user to be impersonated, piggybacking the data which describes the previous user as shown in the following code, which is taken from the UserImpersionation.ImpersonateUser function:

// Declare variables
HttpContext context;
FormsAuthenticationTicket authTicket;
HttpCookie authCookie;
string strSerializedData;

// Store impersonation data in authentication ticket.
context = HttpContext.Current;
strSerializedData = Serialize(context.User.Identity.Name, returnUrl);
authCookie = FormsAuthentication.GetAuthCookie(userName, false);
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
authTicket = new FormsAuthenticationTicket(authTicket.Version, authTicket.Name, 
             authTicket.IssueDate, authTicket.Expiration,
             authTicket.IsPersistent, strSerializedData, authTicket.CookiePath);
authCookie.Value = FormsAuthentication.Encrypt(authTicket);
context.Response.Cookies.Add(authCookie);

First, the user name of the current user and an optional return URL are joined using a simple serialization function. Next, a new authentication cookie is created for the user to be impersonated and stored in the authCookie variable. Since the FormsAuthentication class directly produces an encrypted authentication cookie, this cookie is then decoded to a FormsAuthenticationTicket class stored in the authTicket variable.

Because all properties of the FormsAuthenticationTicket class are read-only, a new FormsAuthenticationTicket class instance is created effectively, cloning the previously obtained FormsAuthenticationTicket class instance, but also carrying our serialized data. This new FormsAuthenticationTicket class instance is then encrypted into our new authentication cookie, which sequentially is added to the response headers.

Retrieving the previous user name

After our new impersonation enabled authentication cookie has been set, it's quite straightforward to obtain the previous user name as shown in the following code, taken from the UserImpersionation.PrevUserName.get property:

// Declare variables
HttpContext context;
FormsIdentity formsIdentity;
string strUserName, strReturnUrl;

// Get data from auth ticket
context = HttpContext.Current;
formsIdentity = (FormsIdentity)context.User.Identity;
if (string.IsNullOrEmpty(formsIdentity.Ticket.UserData))
    return string.Empty;
if (!Deserialize(formsIdentity.Ticket.UserData, 
                 out strUserName, out strReturnUrl))
    return string.Empty;

return strUserName;

First, the User.Identity property which exposes an IIdentity interface by default is casted to an instance of the FormsIdentity class. Note that the User.Identity property only contains a FormsIdentity class instance when the form authentication is used. Since the FormsIdentity class exposes the FormsAuthenticationTicket directly, the previous user name is easily obtained by deserializing the contents of the UserData property.

Redirecting the user on deimpersonation

As you may have noticed while reading the article, an optional redirect URL is stored in the authentication cookie. The UserImpersonation.Deimpersonate() method redirects the user to this location after impersonation is reverted.

In the application I am developing at the moment and use this user impersonation, the support users can impersonate some client user by clicking a button placed on a page describing the details of the concerned client user. I want the whole user impersonation experience to be like starting a new session, and ending thes session cleanly by arriving at the point where the session was started.

Performance

I suspect performance to be hardly affected by using the UserImpersonation class. Yes, the previous user and redirect URL are included in the authentication cookie, but I'm having a hard time believing those 100 extra bytes will kill your performance even the slightest bit.

When considering performance extremes: I suspect this solution to be way more efficient than using the session for storing the previous user name and redirect URL. Assuming your application is running in a web farm, fetching the session will likely require an extra roundtrip toward the database server, taking anywhere between 20 and 100 ms. While I haven't tested this, I expect decrypting the authentication cookie will not even take a single millisecond.

Conclusion

I think I succeeded quite well implementing ASP.NET user impersonation. It surprised me I was unable to find any other examples of the functionality described in this article, since it seems this kind of stuff is likely to be present in most enterprise level applications, even while this whole user impersonation trick was not the most difficult thing to implement.

In this way, I want to contribute something back to the whole Open Source community which basically helped me from learning to program in the first place to solve many problems I now encounter as a professional programmer. As always, feel free to comment, did I miss something, is stuff not working, got a better solution? Drop a comment!

Links and references

History

  • 18/07/2009 - Version 1.0 - Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Christ Akkermans

Netherlands Netherlands
No Biography provided

Comments and Discussions

 
GeneralExcellent Article PinmemberGuy Harwood16-Nov-09 10:10 
This kind of functionality exists in phpBB and can be very useful sometimes.
 
Nice article, well written
 
Obviously TonyM001 has never used software like that Laugh | :laugh:
 
---Guy H Wink | ;-) ---

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 13 Nov 2009
Article Copyright 2009 by Christ Akkermans
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid