Xalryth
Xalryth

Reputation: 53

accessing singleton created within ConfigureServices in the startup.cs file in asp.net core in the same ConfigureServices method

So i've been trying to set up a small test api with token authentication in the form of JWT distribution, the token distribution part worked as intended.

However as i wanted to make the methods for my JWT service more generic to allow for different kinds of signing the tokens(since i would prefer a private/public keypair), i tried to set up some more options within the appsettings files which would then dictate the way that tokens would be generated, i began loading in those settings with dependency injection which i've barely touched up until now.

So the problem came when i wanted to take those configuration classes that i set up as singletons(most of the guides i've read through up until now have done this, so i assume it's somewhat correct) and use them within the ConfigureServices method in which they added, so that i could use the parameters which, in my mind should have been set since i configured a few lines above by getting a section of the appsettings file.

However once i try and access them, i don't get anything back, and am instead left with empty values.

Startup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        //the token config takes values from the appsettings.json file
        var tokenConf = Configuration.GetSection("TokenConfiguration");
        services.Configure<TokenConfiguration>(tokenConf);

        //the signing credentials are assigned in the JwtTokenService constructor
        var signingConf = new SigningConfiguration();
        services.AddSingleton<SigningConfiguration>(signingConf);
        //my token service
        services.AddSingleton<IJwtTokenService, JwtTokenService>();

        //i try to get hold of the actual values to use later on
        var provider = services.BuildServiceProvider();
        TokenConfiguration tc = provider.GetService<TokenConfiguration>();
        SigningConfiguration sc = provider.GetService<SigningConfiguration>();

        //i wanna use the values in here when i set the parameters for my authentication
        services.AddAuthentication(x =>
        {
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(x =>
        {
            x.Events = new JwtBearerEvents
            {
                OnTokenValidated = context =>
                {
                    return Task.CompletedTask;
                }
            };

            x.RequireHttpsMetadata = false;
            x.SaveToken = true;
            x.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                //values used here since i specify issuer, audience and what kind of key to use in the settings
                //the key & credentials differ based on a bool in the settings file and will either be a symmetric or asymmetric key
                ValidIssuer = tc.Issuer,
                ValidAudience = tc.Audience,
                IssuerSigningKey = sc.Key
            };
        });

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseMvc();
    }
}

JwtTokenService.cs (IJwtTokenService only has the CreateToken method which is implemented here)

public class JwtTokenService : IJwtTokenService
{
    private TokenConfiguration tokenConf;
    public SigningConfiguration signingConf;

    public JwtTokenService(IOptions<TokenConfiguration> tc) {
        tokenConf = tc.Value;
        signingConf = new SigningConfiguration();

        //if the asymmetric bool is set to true, assign a new rsa keypair to the signing configuration
        //otherwise, use a symmetric key with a hmac hash
        if (tc.Value.AsymmetricKey)
        {
            using (var provider = new RSACryptoServiceProvider(2048))
            {
                signingConf.Key = new RsaSecurityKey(provider.ExportParameters(true));
            }

            signingConf.SigningCredentials =
                new SigningCredentials(
                    signingConf.Key,
                    SecurityAlgorithms.RsaSha256);
        }
        else {
            signingConf.Key = 
                new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(tc.Value.HmacSecret));

            signingConf.SigningCredentials = 
                new SigningCredentials(
                    signingConf.Key, 
                    SecurityAlgorithms.HmacSha512);
        }
    }

    /// <summary>
    /// Creates a token based on the running configuration
    /// </summary>
    public string CreateToken(List<Claim> claims)
    {
        var token = new JwtSecurityToken(
            issuer: tokenConf.Issuer,
            audience: tokenConf.Audience,
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(tokenConf.Minutes),
            signingCredentials: signingConf.SigningCredentials
            );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

TokenConfiguration.cs

public class TokenConfiguration
{
    public string Audience { get; set; }
    public string Issuer { get; set; }
    public int Minutes { get; set; }
    public bool AsymmetricKey { get; set; }
    public string HmacSecret { get; set; }
}

SigningConfiguration.cs

public class SigningConfiguration
{
    public SecurityKey Key { get; set; }
    public SigningCredentials SigningCredentials { get; set; }
}

appsettings.json

"TokenConfiguration": {
"Audience": "ExampleAudience",
"Issuer": "ExampleIssuer",
"Minutes": 30,
"AsymmetricKey": true,
"HmacSecret": "example-secret-top-secret-secret-is_secret"

}

(the project is running in asp.net core 2.1 in case that matters)

I am new to DI and can't find a lot of examples where the usecase is the same as mine, and most such cases involve actual services rather than adding a 'configuration' class through DI.

There's probably a way better method of doing this, and i'm probably just stupid for not noticing or not knowing what exact thing to google to get a proper answer for it, will most likely be watching/reading a bit about DI after this regardless.

Any input or thoughts would be highly appreciated, as i'm still new to asp.net core and the whole flow of things.

As a small side question regarding the private key generation, in my case, would it be best to store the generated keypair in the keystore, or to store it in memory, or would generating them through something like openSSL and reading from them at startup be the best option?

Upvotes: 5

Views: 6454

Answers (1)

amirani
amirani

Reputation: 270

You are requesting TokenConfiguration instead of IOptions<TokenConfiguration> from ServiceProvider. Change this lines

   TokenConfiguration tc = provider.GetService<TokenConfiguration>();
   SigningConfiguration sc = provider.GetService<SigningConfiguration>(); 

with

    IOptions<TokenConfiguration> tc = provider.GetService<IOptions<TokenConfiguration>>();
    IOptions<SigningConfiguration> sc = provider.GetService<IOptions<SigningConfiguration>>();

And then access options with tc.Value.

Building ServiceProvider in ConfigureServices is not a good idea i would take directly from config by Configuration["TokenConfiguration:Audience"] wherever i need in ConfigureServices.

Upvotes: 3

Related Questions