What is the best way to share a Configuration object used in ASP.NET Core 3.1 Startup and Controllers

723 Views Asked by At

What is the best way to share a configuration object between the ASP.NET Core 3.1 Startup class and Controllers?

I have seen some examples using DI, and this seems like a good idea, but I need to use the dependency within public void ConfigureServices(IServiceCollection services).

Furthermore the object depends on a Microsoft.Extensions.Configuration.IConfiguration instance.

The object will be used within Startup.cs, within the ConfigureServices itself as well as in Controllers.

Would DI work? Or is the solution a Singleton with parameters?

Here is the specific code that needs to be shared:

// openssl rand -hex 16 => 256 bits when read
var jwt_key = Configuration.GetSection("JwtOption:IssuerSigningKey").Value;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwt_key));

var tokenValidationParameters = new TokenValidationParameters
{
    // The signing key must match!
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = signingKey,

    // Validate the JWT Issuer (iss) claim
    ValidateIssuer = true,
    ValidIssuer = "some host name",

    // Validate the JWT Audience (aud) claim
    ValidateAudience = true,
    ValidAudience = "web_intranet",

    // Validate the token expiry
    ValidateLifetime = true,

    // If you want to allow a certain amount of clock drift, set that here:
    ClockSkew = TimeSpan.Zero
};

This object is used as follows within the public void ConfigureServices(IServiceCollection services) method

services
    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {                
        options.TicketDataFormat = new CustomJwtDataFormat(
            SecurityAlgorithms.HmacSha256,
            tokenValidationParameters);
    });
2

There are 2 best solutions below

0
On BEST ANSWER

Try to avoid passing IConfiguration around. The shared code can be done in Startup and the model populated and added to the container

You can register the instance directly with the container in Startup.ConfigureServices

void ConfigureServices(IServiceCollection services) {
    var jwt_key = Configuration.GetSection("JwtOption:IssuerSigningKey").Value;
    var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwt_key));

    var tokenValidationParameters = new TokenValidationParameters {
        //...code omitted for brevity
    }

    services.AddSingleton(tokenValidationParameters);

    //...can still use tokenValidationParameters

    services
        .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options =>
        {                
            options.TicketDataFormat = new CustomJwtDataFormat(
                SecurityAlgorithms.HmacSha256,
                tokenValidationParameters);
        });
}

and inject it explicitly where needed

//ctr
public MyController(TokenValidationParameters tokenParameters) {

    //...

}

Or you can use options pattern

Reference Options pattern in ASP.NET Core

void ConfigureServices(IServiceCollection services) {
    //...code omitted for brevity
    var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwt_key));

    services.Configure<TokenValidationParameters>(options => {    
        // The signing key must match!
        options.ValidateIssuerSigningKey = true;
        options.IssuerSigningKey = signingKey;

        // Validate the JWT Issuer (iss) claim
        options.ValidateIssuer = true;
        options.ValidIssuer = "some host name";

        // Validate the JWT Audience (aud) claim
        options.ValidateAudience = true;
        options.ValidAudience = "web_intranet";

        // Validate the token expiry
        options.ValidateLifetime = true;

        // If you want to allow a certain amount of clock drift, set that here:
        options.ClockSkew = TimeSpan.Zero;
    });

    //...

    //Use DI services to configure cookie options
    var scheme = CookieAuthenticationDefaults.AuthenticationScheme;
    services.AddOptions<CookieAuthenticationOptions>(scheme)
        .Configure<IOptions<TokenValidationParameters>>((options, token) => {
            options.TicketDataFormat = new CustomJwtDataFormat(
                SecurityAlgorithms.HmacSha256,
                token.Value); //<--
        });

    services
        .AddAuthentication(scheme)
        .AddCookie();
}

Reference Use DI services to configure options

and Inject IOptions<TokenValidationParameters> where needed

//ctr
public MyController(IOptions<TokenValidationParameters> options) {

    TokenValidationParameters tokenParameters = options.Value;

    //...

}
1
On

Here is the option I tested using DI.

Create the POCO Object:

public sealed class JwtConfig
{
    public TokenValidationParameters tokenValidationParameters;
    public JwtConfig(IConfiguration configuration)
    {
        // configure the object: tokenValidationParameters
    }
}

In the Startup:

public void ConfigureServices(IServiceCollection services)
{
        // register
        services.AddSingleton<JwtConfig>();

        // retrieve 
        var serviceProvider = services.BuildServiceProvider();
        var jwtConfig = serviceProvider.GetService<JwtConfig>();

        // use the jwtConfig <IMPORTANT to be able to do this here>
}

In the Controller Constructor, inject the object:

    public LoginController(IUserService userService, JwtConfig jwtConfig)
    {
        this.jwtConfig = jwtConfig;
    }