Impersonation
Purpose
The goal if the impersonation feature is to allow a user with the Manager role to act as another user.
In Opinum Data Hub
In Opinum Data Hub, the impersonate feature is available from within the /users page.
The Impersonate action is visible next to the users having only the User role.
Upon clicking the Impersonate action, the browser will refresh and will log you under the impersonated user.
The logo on the right side of the menu indicates that you're currently impersonating someone.
Integrate in your Own application
Most of the work to impersonate a user is done by our IdentityServer (https://identity.opinum.com). However, there are still a few things that must be done on the client side (your Website that is secured by our IdentityServer).
Impersonate
Here are the steps done in Opinum Data Hub to implement the impersonation. Note that only a few are mandatory.
You must be authenticated as a Manager (Mandatory)
You click on an link that Submit a form to send a HTTP POST to https://datahub.opinum.com/impersonation/impersonate containing the target userId in the body.
The Web controller is validating the two users (must be on the same account, one Manager, the second User, + your business logic) and then re-initiate the login flow by returning a new ChallengeResult. The OwinContext is used to communicate more parameter to the next step.
[HttpPost] public async Task<ActionResult> Impersonate(ImpersonateModel model) { _logger.Info($"Starting impersonated userId<{model.UserId}>"); // // some busines login here // if (CanImpersonate(...)) { _logger.Debug("User found, sending challenge"); HttpContext.GetOwinContext().Set("Impersonate", true); HttpContext.GetOwinContext().Set("UserToImpersonate", model.UserId); return new ChallengeResult(OpenIdConnectAuthenticationDefaults.AuthenticationType, Url.Action("Index", "Home")); } return RedirectToAction("Index", "Home"); }
class ChallengeResult
/// <summary> /// Copied from .net core /// </summary> public class ChallengeResult : HttpUnauthorizedResult { #region Fields // Used for XSRF protection when adding external logins public const string XsrfKey = "XsrfId"; public string LoginProvider { get; set; } public string RedirectUri { get; set; } public string UserId { get; set; } #endregion #region Constructors public ChallengeResult(string provider, string redirectUri) : this(provider, redirectUri, null) { } public ChallengeResult(string provider, string redirectUri, string userId) { LoginProvider = provider; RedirectUri = redirectUri; UserId = userId; } #endregion #region Base Methods Override public override void ExecuteResult(ControllerContext context) { var properties = new AuthenticationProperties { RedirectUri = RedirectUri }; if (UserId != null) { properties.Dictionary[XsrfKey] = UserId; } context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider); } #endregion }
Because we sent a ChallengeResult in the previous step, we're reaching the OpenIdConnectAuthenticationNotifications.RedirectToIdentityProvider method.
- This method is already used to prepare the AcrValues that will be sent to IdentityServer.
- Check if the OwinContext has a parameter telling us that the impersonation flow must be initiated.
- If it is the case, add an additional acrValue "Impersonate:TheIdOfTheUserToImpersonate" (Mandatory)
- Force IdentityServer to validate the user even if the current user is already authenticated by specifying the Prompt:Login parameter https://identityserver.github.io/Documentation/docsv2/endpoints/authorization.html (Mandatory)
if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.AuthenticationRequest) { if (await Impersonate(notification)) { var impersonateUserId = notification.OwinContext.Get<string>("UserToImpersonate"); notification.ProtocolMessage.AcrValues += $" Impersonate:{impersonateUserId}"; notification.ProtocolMessage.Prompt = OidcConstants.PromptModes.Login; } }
You are redirected to IdentityServer that will, based on the AcrValue, impersonate the user
IdentityServer will redirect you back to the ClientApplication.
Note
The ClaimsIdentity returned by IdentityServer contains additional claims that you can use to drive your ClientApplication
Revert Impersonate
Once you are impersonating a user, you can stop impersonating either by doing a full Logout or by using the following flow
Here is how we implemented the Revert Impersonate in Opinum Data Hub
- You must be authenticated and impersonating someone (Mandatory)
- You click on an link that Submit a form to send a HTTP POST to https://datahub.opinum.com/impersonation/revertimpersonate.
- The Web controller re-initiate the login flow by returning a new ChallengeResult. The OwinContext is used to communicate the RevertImpersonate action to the next step.
[HttpPost] public async Task<ActionResult> Revert() { HttpContext.GetOwinContext().Set("RevertImpersonate", true); return new ChallengeResult(OpenIdConnectAuthenticationDefaults.AuthenticationType, Url.Action("Index", "Home")); }
- Because we sent a ChallengeResult in the previous step, we're reaching the OpenIdConnectAuthenticationNotifications.RedirectToIdentityProvider method.
- Check if the OwinContext has a parameter telling us to revert the impersonation to your initial User
- If it is the case, add an additional acrValue "RevertImpersonate:true" (Mandatory)
- Force IdentityServer to validate the user even if the current user is already authenticated by specifying the Prompt:Login parameter https://identityserver.github.io/Documentation/docsv2/endpoints/authorization.html (Mandatory)
if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.AuthenticationRequest) { if (await RevertImpersonate(notification)) { notification.ProtocolMessage.AcrValues += $" RevertImpersonate:true"; notification.ProtocolMessage.Prompt = OidcConstants.PromptModes.Login; } }
- You are redirected to IdentityServer that will, based on the AcrValue, stop the impersonation and revert to your initial User
- IdentityServer will redirect you back to the ClientApplication.