How to correctly get dependent scoped services from ISecurityTokenValidator

Viewed 5690

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...

1 Answers

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.

Related