How do I use ASP.NET Identity 2.0 to allow a user to impersonate another user?
Asked Answered
C

2

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?

Chestnut answered 11/6, 2014 at 11:28 Comment(1)
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.Propolis
C
66

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

Chestnut answered 12/6, 2014 at 10:45 Comment(11)
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.Untwist
@Untwist do you mean it has been updated in v3? I see last update to v2 branch was in April 2015Chestnut
Hi @Chestnut 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<T>. 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.Untwist
@Untwist huh.. I guess I've missed that somehow... or ignored.Chestnut
Hi @trailmax, why are you using when impersonnating HttpContext.Authentication.SignInAsync instead of _signInManager.SignInAsync?Beet
@Swell I wish I remember - that was a while ago and I don't remember the exact reasoning.Chestnut
I was reading the code on my phone. The reason is you can't use the sign in manager with a ClaimsPrincipal.Beet
@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!Chestnut
You're a ledge mateSubteen
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.Bromberg
@Bromberg Yes, indeed, can be done. But I call that a feature, not a bug :-)Chestnut
J
-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");
    }
Janijania answered 6/2, 2017 at 1:33 Comment(4)
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.Maretz
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 .Janijania
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.Maretz
@GiulioFronterotta see my updated answer for impersonation in CoreChestnut

© 2022 - 2024 — McMap. All rights reserved.