Why will you want to impersonate a user? Isn’t it a bad thing to do? when should it be used? Let’s say you have a user of your system, this user faces an issue or a bug he/she desperately tries to explain but you just can’t understand what he is explaining. Wouldn’t it be good if you could sign in to your system as that user? see what he sees and correct any issues faced? Or maybe you want to test API calls when building an API and quickly want to make API calls as a given user. All of the above examples, if you’re using OpenId connect and Asp.net core, will require you to implement OpenId User Impersonation in Asp.net core. That is exactly what we will cover in this article. Disclaimer, this is for learning purposes only. What you do with this in your system or any other system is your responsibility.
This article about 4 Structured Logging Techniques in ASP.net core Every Developer Should Know (Dotnet 7+) might interest you. I wrote it for dotnet devs just like you.
What Will We Implement?
Taking the above scenarios into consideration, In this tutorial, we will add to an existing openId connect Identity provider, the capacity to generate authentication tokens for users whose emails are given to it. Basically, that means, you make a request to the identity provider, with an email, and it sends you a valid authentication token for the user with the given email.
If you find this article useful, please follow me on Twitter, Github, Linkedin, or like my Facebook page to stay updated.Follow me on social media and stay updated
Implementing OpenId User Impersonation in Asp.net core
As you might have guessed, what you need before following this tutorial is to use Duende’s Identity server libraries for OpenId connect. That’s what I’ll be using for this tutorial. If you’re not familiar with the Duende Identity server, check this documentation. By the way, this article assumes you’re familiar with it and you’re using it too.
Step 1: Create a custom grant that receives a user’s email address and produces a token
Grants are used to specify how the client will interact with the identity provider. Our grant will work like the client credentials grant type. The only difference is that it will take a user’s email and create a token for that specific user. So, only valid clients approved by the system will be able to use this impersonation.
To create a custom grant, you need to create a GrantValidator that will inherit from:
1 | IExtensionGrantValidator |
And the validation logic should be found in this specific method of the validator
1 | public async Task ValidateAsync(ExtensionGrantValidationContext context) |
The grant validation process is easy. For the sake of brevity, I’ll paste the whole code for the grant validator here below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | public class UserImpersonationGrantValidator : IExtensionGrantValidator { public string GrantType => "user_impersonation"; private readonly ILogger<UserImpersonationGrantValidator> _logger; private readonly UserManager<User> _userManager; public UserImpersonationGrantValidator(ILogger<UserImpersonationGrantValidator> logger, UserManager<User> userManager) { _logger = logger; _userManager = userManager; } public async Task ValidateAsync(ExtensionGrantValidationContext context) { try { _logger.LogInformation("Performing impersonation grant."); var scopes = context.Request.RequestedScopes; var claims = new List<Claim>(); var email = context.Request.Raw.Get("email"); if (string.IsNullOrEmpty(email)) { _logger.LogWarning("Impersonation grant called without user email."); context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); return; } if (scopes != null && scopes.Any()) { foreach (var scope in scopes) { claims.Add(new Claim("scope", scope)); } } var user = await _userManager.FindByEmailAsync(email); if (user == null) { _logger.LogWarning("An unknown user email was used to request this API. Token"); context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); return; } claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id)); context.Result = new GrantValidationResult( subject: user.Id, authenticationMethod: GrantType, claims: claims); return; } catch (Exception e) { _logger.LogError(e, "Fatal error occurred while performing impersonation grant."); throw; } } } |
To make the identity provider aware of your new grant, you need to register your grant validator and, to register your new grant validator, use this code snippet.
1 2 3 4 5 6 7 8 9 | builder.Services .AddIdentityServer(options => { ... }) ... ... .AddExtensionGrantValidator<UserImpersonationGrantValidator>() |
Now, you need to tell the appropriate client that they should accept our grant for validation.
1 | client.AllowedGrantTypes = { "user_impersonation" }; |
If you find this article useful, please follow me on Twitter, Github, Linkedin, or like my Facebook page to stay updated.
Follow me on social media and stay updated
Step 2: Impersonate a user
To finish, we have to use our grant to impersonate a user present in our system. This is the easiest part. For this tutorial, I’ll show you how to do it with Postman. Below is the first part of the request.
Here is how you authenticate the request on postman
Conclusion
Now you know how to impersonate users when you want to find bugs or correct issues on your system. I hope this article was useful. In case it was, please don’t hesitate to share and let me know about it on social media. Thanks for reading till the end.
Follow me on social media and stay updated