ASP.NET Core 5 JwtBearer dynamic secret based on User

Viewed 135

I'm trying to create simple API with JWT authentication, where each user has different secret key, instead of one global secret. Which sounds like simple problem, but the tricky question is how to correctly get UserManager in StartUp for validation.

First and obvious approach would be via Dependency Injection, looking like that:

public IConfiguration Configuration { get; }
public UserManager<IdentityUser> UserManager { get; }

public Startup(IConfiguration configuration, UserManager<IdentityUser> userManager)
{
        Configuration = configuration;
        UserManager = userManager;
}       

But that will immediately crash with error: "System.InvalidOperationException: 'Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1"

So second attempt was with BuildServiceProvider:

public void ConfigureServices(IServiceCollection services)
{
       ...
       UserManager<IdentityUser> userManager = services.BuildServiceProvider().GetService<UserManager<IdentityUser>>();
       ...
}

That actually works, but with that ugly warning: "ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependecy injecting services as parameters to 'Configure'."

After some time i came up with third final solution:

private IApplicationBuilder _app;

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
       ...
       _app = app;
}

public void ConfigureServices(IServiceCollection services)
{
       ...
       services.AddAuthentication().AddJwtBearer(options => {
           options.TokenValidationParameters = new TokenValidationParameters()
           {
               ValidateIssuer = true,
               ValidateAudience = true,
               ValidateLifetime = true,
               ValidIssuer = Configuration["JWT:Issuer"],
               ValidAudience = Configuration["JWT:Audience"],
               IssuerSigningKeyResolver = (token, securityToken, kid, validationParameters) =>
               {
                   List<SecurityKey> keys = new List<SecurityKey>();

                   JwtSecurityToken jwtToken = (JwtSecurityToken)securityToken;
                   Claim claimName = jwtToken.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name);
                   if (claimName != null)
                   {
                       string name = claimName.Value;
                       using IServiceScope serviceScope = _app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope();
                       UserManager<IdentityUser> userManager = serviceScope.ServiceProvider.GetService<UserManager<IdentityUser>>();
                       IdentityUser user = userManager.FindByNameAsync(name).GetAwaiter().GetResult();

                       if (user != null) keys.Add(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(user.JwtSecret)));
                   }

                   return keys;
               },
               //IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"])),
               ClockSkew = TimeSpan.Zero
           };
       });
}

That one solution works, no warnings or bugs so far, but is it clean? Can i improve it or should i take different approach i didnt think of? Do not be so harsh on me - i am pretty much newbie with ASP ;)

If You have made it so far and/or will give me any tips i will be more than appreciated. Thanks.

0 Answers
Related