Search code examples
c#asp.net-coredependency-injection.net-8.0

ASP.NET Core 8 : dependency injection in Startup.ConfigureServices


I'm working on an ASP.NET Core 8 web app. I have a service for providing keys for signing JSON web tokens (to be used for authentication). The service is a dependency of JWT authentication.

I wonder how to provide the service in the authentication setup? I can't DI it in the ConfigureServices method, because trying to do so would cause an error.

Here's the code (its relevant parts):

public class Startup()
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IKeyProvider, KeyProvider>();
        
        services.AddAuthentication(o =>
        {
            o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(o =>
        {
            // ...
            // here below I need DI to provide a KeyProvider instance:
            IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
            //...
        });
    }

    public void Configure()
    {
        // ...
    }
}

Any idea on how to solve this?

ADDED 2025-01-26 ->

More code on a typical JWT auth setup:

services.AddAuthentication(o =>
{
    o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
    o.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = "SomeIssuer",
        ValidAudience = "SomeAudience",
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("ABCDEABCDEABCDEABCDEABCDEABCDEABCDE")),
        ClockSkew = new TimeSpan(0)
    };
});

Solution

  • While AddJwtBearer allows you to configure JwtBearerOptions, there is currently no overload that allow for the use DI out of the box.

    Within the default AuthenticationBuilder class there is a helper method used to add authentication schemes along with its strongly typed options configuration which is supposed to be derived from AuthenticationSchemeOptions. See source code here if interested.

    Taking advantage of the functionality available in the Options pattern in ASP.NET Core and referencing "Use DI services to configure options", the following example shows how services can be accessed from dependency injection while configuring options.

    In this case JwtBearerOptions

    public void ConfigureServices(IServiceCollection services) {
        //make sure service registered
        services.AddSingleton<IKeyProvider, KeyProvider>();
                    
        //adding authentication
        var authenticationScheme = JwtBearerDefaults.AuthenticationScheme;
        services.AddAuthentication(o => {
            o.DefaultAuthenticateScheme = authenticationScheme;
            o.DefaultChallengeScheme = authenticationScheme;
        })
        .AddJwtBearer(); //<-- Don't forget to include this so `JwtBearerOptions` is added to configuration
    
        //now configuring options
        services
            .AddOptions<JwtBearerOptions>(authenticationScheme) //Doing this to get access to OptionsBuilder<TOptions>
            .PostConfigure<IKeyProvider>((options, keyProvider) => { //Use DI services to configure options
                //Using OptionsBuilder<TOptions>.PostConfigure
                //Note: These are run after all Configure(Action<TOptions>).
                
                options.TokenValidationParameters = new TokenValidationParameters {
                    //...omitted for brevity
    
                    IssuerSigningKeyResolver = (string token, SecurityToken securityToken,string identifier, TokenValidationParameters parameters) => {
                        List<SecurityKey> keys = new List<SecurityKey>();
                        //...
                        //use KeyProvider instance to get keys
                        //keys.Add(signingKey);
                        //...
                        return keys;                    
                    },
    
                    //...omitted for brevity
                };
            });
    }
    

    Note that OptionsBuilder<TOptions> provides overloads of Configure and PostConfigure that allow the use of five services to configure options.

    This will allow for more services if needed to satisfy the desired functionality.