42

I'm migrating a ASP.NET MVC 5.1 application from MembershipProvider to ASP.NET Identity v2.0. One of the features I have in the application is user impersonation: Administrators can be logged in as any other user registered on the site without knowing passwords.

I used this code to implement user impersonation for the MembershipProvider and this does not work with Identity library.

How do I implement user impersonation (not IIS impersonation) in ASP.NET Identity?

George Stocker
  • 57,289
  • 29
  • 176
  • 237
trailmax
  • 34,305
  • 22
  • 140
  • 234
  • A question looking for off-site resources is off-topic. I've edited your question to allow for answers that are on-topic, and it still achieves the same ends. – George Stocker Jun 11 '14 at 11:55

2 Answers2

67

I've found a solution to this problem.

Basically I add claim with admin username, if this claim exists, I know that impersonation is happening. When admin wants to stop impersonation, system retrieves original username for the claims, deletes old impersonated-cookie and creates a new cookie for the admin:

[AuthenticateAdmin] // <- make sure this endpoint is only available to admins
public async Task ImpersonateUserAsync(string userName)
{
    var context = HttpContext.Current;

    var originalUsername = context.User.Identity.Name;

    var impersonatedUser = await userManager.FindByNameAsync(userName);

    var impersonatedIdentity = await userManager.CreateIdentityAsync(impersonatedUser, DefaultAuthenticationTypes.ApplicationCookie);
    impersonatedIdentity.AddClaim(new Claim("UserImpersonation", "true"));
    impersonatedIdentity.AddClaim(new Claim("OriginalUsername", originalUsername));

    var authenticationManager = context.GetOwinContext().Authentication;
    authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
    authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, impersonatedIdentity);
}

More information is in my blog-post: User impersonation with ASP.Net Identity 2.

User Impersonation in Asp.Net Core

Upd July 2017: this topic is quite popular, so I've looked into user impersonation in Core and principles are very similar with updated API. Here is how to impersonate:

    [Authorize(Roles = "Admin")] // <-- Make sure only admins can access this 
    public async Task<IActionResult> ImpersonateUser(String userId)
    {
        var currentUserId = User.GetUserId();

        var impersonatedUser = await _userManager.FindByIdAsync(userId);

        var userPrincipal = await _signInManager.CreateUserPrincipalAsync(impersonatedUser);

        userPrincipal.Identities.First().AddClaim(new Claim("OriginalUserId", currentUserId));
        userPrincipal.Identities.First().AddClaim(new Claim("IsImpersonating", "true"));

        // sign out the current user
        await _signInManager.SignOutAsync();

        // If you use asp.net core 1.0
        await HttpContext.Authentication.SignInAsync(cookieOptions.ApplicationCookieAuthenticationScheme, userPrincipal);
        // If you use asp.net core 2.0 (the line above is deprecated)
        await HttpContext.SignInAsync(cookieOptions.ApplicationCookieAuthenticationScheme, userPrincipal);

        return RedirectToAction("Index", "Home");
    }

This is how to stop impersonation:

    [Authorize(Roles = "Admin")] // <-- Make sure only admins can access this 
    public async Task<IActionResult> StopImpersonation()
    {
        if (!User.IsImpersonating())
        {
            throw new Exception("You are not impersonating now. Can't stop impersonation");
        }

        var originalUserId = User.FindFirst("OriginalUserId").Value;

        var originalUser = await _userManager.FindByIdAsync(originalUserId);

        await _signInManager.SignOutAsync();

        await _signInManager.SignInAsync(originalUser, isPersistent: true);

        return RedirectToAction("Index", "Home");
    }

Full explanation in my blog: http://tech.trailmax.info/2017/07/user-impersonation-in-asp-net-core/ Full code sample on GitHub: https://github.com/trailmax/AspNetCoreImpersonation

George Johnston
  • 31,652
  • 27
  • 127
  • 172
trailmax
  • 34,305
  • 22
  • 140
  • 234
  • 1
    Awesome work. It seems like the implementation of `SecurityStampValidator` has been updated to be culture-aware, so there is a bit more complexity to creating a modified version of this class as it has internal dependencies that also now need to be cribbed (surprised at the non-extensible nature of it TBH). Easy enough. Works really well. – spender Dec 01 '16 at 11:33
  • @spender do you mean it has been updated in v3? I see last update to v2 branch was in April 2015 – trailmax Dec 02 '16 at 10:44
  • Hi @trailmax In nuget package `Microsoft.AspNet.Identity.Owin` version 2.2.1, there are several calls from `SecurityStampValidator` to an extension method `Microsoft.AspNet.Identity.TaskExtensions.WithCurrentCulture`. Because the `TaskExtensions` class is `internal` (i.e. not visible from my assembly), it means I had to copy that over too to get it to work. – spender Dec 02 '16 at 10:54
  • @spender huh.. I guess I've missed that somehow... or ignored. – trailmax Dec 02 '16 at 11:03
  • Hi @trailmax, why are you using when impersonnating HttpContext.Authentication.SignInAsync instead of _signInManager.SignInAsync? – JuChom Sep 17 '17 at 13:24
  • @Swell I wish I remember - that was a while ago and I don't remember the exact reasoning. – trailmax Sep 17 '17 at 18:07
  • I was reading the code on my phone. The reason is you can't use the sign in manager with a ClaimsPrincipal. – JuChom Sep 18 '17 at 07:04
  • @Swell I see you done the code edit - thanks for this! Recently there has been way to many updates to keep my hands on the pulse! – trailmax Sep 19 '17 at 10:51
  • 1
    You're a ledge mate – Andrew Feb 16 '18 at 18:35
  • Awesome work! But I think there is a bug. There is a possibility for admin to impersonate second time without calling StopImpersonation. In this case OriginalUserId will be wrong (will be previous impersonated user rather than original). So I'd add a check to ImpersonateUser to disallow serial impersonation similarly to a check you do in StopImpersonation. – Sasha Jan 06 '20 at 12:30
  • @Sasha Yes, indeed, can be done. But I call that a feature, not a bug :-) – trailmax Jan 06 '20 at 14:02
-2

Just for who is using the Asp Net Core Identity, this is the code to solve the initial problem (An Administrator that want to enter as user, without knowing password). The following solution is not a real "Impersonation" with token as in the voted answer :

   [Authorize("Administrator")]
   public async Task<IActionResult> ImpersonateUserAsync(string email)
    {            
        var impersonatedUser = await _userManager.FindByNameAsync(email); //Usually username is the email
        await _signInManager.SignOutAsync(); //signout admin
        await _signInManager.SignInAsync(impersonatedUser,false); //Impersonate User

        return RedirectToAction("Index","Home");
    }
  • 3
    This isn't impersonation, it's just signing in as a different user. For real impersonation, you need to remain logged in as the original user. – DavidG Feb 06 '17 at 01:40
  • 1
    Ok, David, it's true. But the first question of Trailamx was:"Administrators can be logged in as any other user registered on the site without knowing passwords" . With the method above, decorated, for example, by [Authorize("Administrator")] using Asp.Net Identity, you solved the original problem, in Asp.Net Core . Note that "Administrator" in the Authorize statement must be a role configured in Asp.Net Identity System Table . – Giulio Fronterotta Feb 07 '17 at 14:03
  • Yes, it's still a bit of a hack though, that's my real issue with it. You would also be forced to log back in as the admin once you've done whatever you need to do as the impersonated user. That's what makes this method not particularly viable for me. – DavidG Feb 07 '17 at 15:20
  • @GiulioFronterotta see my updated answer for impersonation in Core – trailmax Jul 26 '17 at 00:45