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:

Quick start

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

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.

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:

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}':

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:

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:

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:

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!

Comments and Discussions

Has anybody got this working with MVC? It seems like a great solution, but I'm not having any luck getting the control to render on the page. (In design view, I get "Error Creating Control - LoginUserImpersonation...No HttpContext available. Unable to complete operation.")

It won't work in the designer because the designer is just a simulated view of a page/control. When viewing through the designer the control is not rendered in the context of a web server request, thus there is no HttpContext available.

I didn't use the control either (all it does is render a link anyway), but I've modified my UserImpersonation.cs as follows to make it a little easier to work with in an MVC app:

I've always just used FormsAuthentication.RedirectFromLoginPage(UserName, True) to do my impersonating. The user has to then log out and then log back into the admin account. I like how yours tracks the user so they can just log back in with the click of a button.

And why would that be? When I search for 'ASP.NET user impersonation' for example, all I get is stuff about impersonating windows users for accessing network shares and stuff. Well perhaps I didn't search for the completely correct term, English is not my native language. But as you can see may English surely isn't that bad so please, at least clearly point out what I did wrong when saying so.

All right, we seem to have some misunderstanding here. Of course it's in the real world undesirable for an actual systems administrator for take for example a webmail application to be logging in as random users and sniffing around.

But I actually have several people sitting next to their phone at our office using this functionality every day. My company offers some kind of software as a service, our (trusted) support employees handle support calls from client users who have trouble getting stuff done in our application. If the issue isn't resolved directly, our support users user the in this article described functionality to log in as the troubled user 'and be able to see what the actual user sees'. Out support users then either talk the client trough the procedure for whatever they want done or do it themselves directly. Half of the time out support users even just get called and asked to perform some operations for the clients. I presume you let your support users reset passwords every time some client needs assistance?

And who said any support/administrative user gains access to literally any user he of she likes using my code? If you would have taken a look at the code you would have seen it's completely up to the developer to specify which user to impersonate. My support users using this very piece of code certainly are not able to login onto client accounts of clients in different departments, heck they probably don't even know they exists.

Also if you do not have any problem with my code, only with the application of it, why vote a 2? This article surely does not provide any means of implementing the in your opinion incorrect application directly, only the described functionality. More than two third of the article is about how this authentication cookie stuff actually works. Don't you think that'll actually be interesting for less advanced users to read?

Well perhaps I should change the introduction of this article to make more clear this functionality isn't really targeted at administrator users, but more toward the support users.

There are not only administrators searching articles here. Maybe you are standing at the administrator position, you can share your point of view of course but please don't assume every administrator doesn't need every thing you don't need. Moreover, developers are here too. And this article is good and useful. At least I think it's very useful when testing functionality of different roles of a website. I vote 5.

I am a software designer/developer for a small software house so my solutions have to be generic. I do not write programs for administrators but do have to write programs that management, IT administrators and end users are happy with. If I do not, our company goes bust.

Your solution looks at things purely from a developer point of view - admirable, but utterly impractable in the real world.

Miller, I have no problem with the code but you have to think beyond programming. Put yourself in my shoes, you have just sold a Document Management system and a bug has been reported. I say, its ok I can sign on as that user and sort it out.

Miller, I have no problem with the code but you have to think beyond programming. Put yourself in my shoes, you have just sold a Document Management system and a bug has been reported. I say, its ok I can sign on as that user and sort it out.

Yes, your opinion is correct, but that's out of the topic here. He's sharing his article and that article is useful for some people. He's not sharing an article called "A product for sale". Now there's someone saying "Oh no, your solution is impracticable for end users", what's the point? That's not a question of a technology implementation, that should be a question of the market.

Writing an article is not a simple thing like pressing a submit button. Before asking someone to put himself in your shoes, why don't you put yourself in Christ's shoes and see what happens? Everyone thought he's situation is generic.

But anyway, I'm not against to the score you have voted, I'm just thinking your opinion maybe out of the topic.

This is a very good article for its intended purpose of introducing the concept of user impersonation. An organization’s authorization, access and roles are an entirely different subject and may vary to opposite ends of the spectrum. Unless you are a very small organization, you wouldn’t want your front line customer help desk personnel freely accessing and impersonating the president or CEO. But there will always be a need for someone in IT like the support programmer/developer to have access. Unless you encrypt all your data your database administrator will have access to everything too. The beauty of this is you don’t have to ask, for what should be encrypted passwords, to gain access for trouble shooting and to quickly see what is going on. In large complicated applications setting up test cases and test users is very complicated time consuming business.

I know it has been years since this article was posted, but I feel compelled to comment. First, it's still difficult to find much information on this topic, and I'm really glad I found this. Second, tonym001, you're an idiot! If I told my clients that I couldn't see data the same way they saw it, they wouldn't buy my product. If I had to look up each password (unencrypted!) or ask each customer what their password is, or expect them to reset their password after I looked at their account, they wouldn't buy my product.

On code project, people share solutions that are in use to solve their day-to-day problems. It does not mean that all solutions on code project would solve your exact problem but help you find a better solution.

To me, this community is backbone of my knowledge. When i lack something or feel myself the my knowledge and experience is not enough to solve my admin/development/designing problem i just direct "codeproject C# asp.net impersonation". And do find good articles on top.

When i feel its not good enough on codeproject then i replace cp with msdn or others... and i find what i need to do.

You just cant rate this is for admin that is for developer... in this community people share their thoughts and their real life experience.... author of this article shared his way of work around for a specific problem and he is successful at least in the boundaries of his company. If this solution fits your need. Adopt it otherwise move on for a better match.