12

In my asp.net core 2.0 web app, I've got a custom ISecurityTokenValidator which validates tokens.

It depends on a repository to do a db lookup - the repository itself is setup as a scoped dependency:

services.AddScoped<IMyRepository>(MyRepository);

Now the funkiness comes about because of the way the ISecurityTokenValidator is setup.

It's added in ConfigureServices:

.AddJwtBearer(options =>
    {
        options.SecurityTokenValidators.Clear();
        options.SecurityTokenValidators.Add(new MyTokenValidator(services.BuildServiceProvider()));
    })

This is how it looks:

public class MyTokenValidator : ISecurityTokenValidator
{
    private readonly IServiceProvider _serviceProvider;

    public MyTokenValidator(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public bool CanReadToken(string securityToken) => true;

    public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters,
        out SecurityToken validatedToken)
    {

        var serviceScopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();

        using (var scope = serviceScopeFactory.CreateScope())
        {
            var myRepository = scope.ServiceProvider.GetService<IMyRepository>();
            var principalFactory = scope.ServiceProvider.GetService<IUserClaimsPrincipalFactory<User>>();

            // Use the repo....

        }

    }
}

Now, because the IsecurityTokenProvider is only instantiated once, it's effectively a singleton. When I use the service provider to ask for a IMyRepository I was finding that I was always received the same object - there is no new scope as far as it was concerned, because it's in a singleton class.

To get round that, you'll see in the code above Ive had to manually force a new scope every time the token validator is called. Is this really the only way to resolve this, it seems like I'm hacking around to make it work here...

Matt Roberts
  • 26,371
  • 31
  • 103
  • 180
  • I am having the exact same issue, trying to retrieve the `UserManager` in a token validator. – Mikeyg36 Feb 12 '18 at 03:09
  • I ended up refactoring all this away - I didn't need to hang off jwt Auth, so I created my own "API key" Auth middleware – Matt Roberts Feb 12 '18 at 08:30
  • What do you mean you didn't need to hang off jwt Auth? In my case, I am trying to authorize using JWT tokens issued by Google OAuth from the client side. So it seems I need to wire up my own Google JWT token validator class that inherits from `ISecurityTokenValidator` and implementes `ValidateToken`, where I can then delegate all the token validation to a method in a class provided by google: `Google.Apis.Auth.GoogleJsonWebSignature.ValidateAsync` – Mikeyg36 Feb 13 '18 at 13:17
  • I was using `.AddJwtToken` but I was using my own ISecurityTokenValidator which didn't actually need to do anything with JWT Tokens. So I ripped it all out, and replaced with my own `AuthenticationHandler` (probs not applicable to your scenario) – Matt Roberts Feb 13 '18 at 13:27
  • ah I see. I explained my need and approach here if you are interested: https://stackoverflow.com/questions/48727900/google-jwt-authentication-with-aspnet-core-2-0/48768183#48768183 – Mikeyg36 Feb 13 '18 at 13:48

1 Answers1

26

Old question but the best way I have found to solve this problem is to use IPostConfigureOptions<JwtBearerOptions> to configure SecurityTokenValidators.

First register the JWT bearer and options

        services.AddAuthentication(options =>
        {
            ...
        }).AddJwtBearer(AuthenticateScheme, options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ...
            };
        });

Then register a custom implementation of IPostConfigureOptions<JwtBearerOptions>

    services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>, CustomJwtBearerOptionsPostConfigureOptions>();

And register a custom implementation of ISecurityTokenValidator

    services.AddSingleton<MyCustomSecurityTokenValidator>();

CustomJwtBearerOptionsPostConfigureOptions could look something like:

public class CustomJwtBearerOptionsPostConfigureOptions : IPostConfigureOptions<JwtBearerOptions>
{
    private readonly MyCustomSecurityTokenValidator _tokenValidator; //example dependancy

    public CustomJwtBearerOptionsPostConfigureOptions(MyCustomSecurityTokenValidator tokenValidator)
    {
        _tokenValidator = tokenValidator;
    }

    public void PostConfigure(string name, JwtBearerOptions options)
    {
        options.SecurityTokenValidators.Clear();
        options.SecurityTokenValidators.Add(_tokenValidator);
    }
}

Now options.SecurityTokenValidators is configured by CustomJwtBearerOptionsPostConfigureOptions which is instantiated by dependency injection and can pass on the relevant decencies.

herostwist
  • 3,778
  • 1
  • 26
  • 34
  • 1
    This is the only solution to this problem I've found so far, and it's neat. Wish it had more visibility in SO. Thanks. – mihalios Nov 26 '20 at 21:57
  • 1
    After trying a ton of different dead-ends, this ended up the only thing that really worked well without going off-the-deep-end custom. Confirmed working in .net5.0 – WhiteRuski Jan 23 '21 at 05:01
  • 1
    I never knew about `IPostConfigureOptions`! Works perfectly fine :) – cmxl Apr 21 '21 at 12:51