We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept
Card image cap

Upgrading to .NET Core 3.0 - JWT Bearer with Strongly Typed Configuration

Upgrade to .NET Core 3.x JWT Authentication  • Posted 6 months ago

In the first article of the upgrading to DotnetCore 3.0 series, we were getting started on the road to upgrade by looking at what has changed in the latest iteration and in the article that followed we looked at how we were able to solve an erroneous Swagger configuration that had to be changed in the latest upgrade. In this article let's look at another challenge we faced in the form of requiring to configure the JWT bearer authentication header in the service pipeline by injecting a strongly typed configuration class.

Upgrading to .NET Core 3.0 - Troubleshooting the Swagger UI Ugrading to .NET Core 3.0 - Getting Started

Setting up the Context - The Issue with JWT Bearer Configuration:

The ReadersAPI application written in dotnet core 2.2 had a jwt bearer authentication and authorization placed on the pipeline before the endpoints and every request had to be authenticated and authorized for to access the resources. Configuring the JWT bearer authentication involved two steps like as configuring every other middleware in dotnetcore; registering the middleware on the service container and then configuring the middleware on the execution pipeline. This happens in the ConfigureServices and Configure methods respectively as shown below:


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

        public IConfiguration Configuration { get; }

        //Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IConfigManager, ConfigManager>();
            // build the service pipeline and 
            // fetch the service instance of ConfigManager to pass to the JWT Authentication Middleware
            var sp = services.BuildServiceProvider();
            var config = sp.GetRequiredService<IConfigManager>();
	
	    //register the authentication middleware
            services.AddBearerAuthentication(config);
        }

        //Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            //configure the authentication to execute on request
            app.UseAuthentication();
        }
    }
}

Observe that we have used an extension method AddBearerAuthentication() which takes in a parameter of the singleton instance IConfigManager and is defined as shown below:


namespace ReadersApi
{
    static class AuthorizationExtension
    {
        public static IServiceCollection AddBearerAuthentication(this IServiceCollection services, IConfigManager configManager)
        {
            services.AddAuthentication(options =>
                        {
                            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                        })
                        .AddJwtBearer(options =>
                        {
                            options.TokenValidationParameters = new TokenValidationParameters()
                            {
                                ValidateAudience = true,
                                ValidateIssuer = true,
                                ValidateLifetime = true,
                                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configManager.key)),
                                ValidIssuer = configManager.Issuer,
                                ValidAudience = configManager.Audience
                            };

                            options.Events = new JwtBearerEvents()
                            {
                                OnAuthenticationFailed = (context) =>
                                {
                                    Console.WriteLine(context.Exception);
                                    return Task.CompletedTask;
                                },

                                OnMessageReceived = (context) =>
                                {
                                    return Task.CompletedTask;
                                },

                                OnTokenValidated = (context) =>
                                {
                                    return Task.CompletedTask;
                                }
                            };
                        });

            return services;
        }
    }
}

With this we can come to a conclusion that the extension method aka the Authentication middleware pretty much depends on the IConfigManager class which is a strongly typed configuration class as shown below:


namespace ReadersApi.Providers
{
    public interface IConfigManager
    {
        string Issuer { get; }
        string Audience { get; }
        int ExpiryInMinutes { get; }
        string key { get; }
        string ConnectionString { get; }
    }
    public class ConfigManager : IConfigManager
    {
        private readonly IConfiguration _config;

        public ConfigManager(IConfiguration config)
        {
            _config = config;
        }
	
	....
    }
}

This all went well till the time we were in dotnetcore 2.2, and when we upgraded to dotnetcore 3.0 we realized that this no more works for the new IHost interface the new version now uses in place of the legacy IWebHost interface which supported partial service pipeline building which can then be used in the ConfigureServices() method. But this was disadvantageous because of the service pipeline being built twice consuming performance overhead.


Startup.cs(32,22): warning ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'. [E:\DotNet Core Projects\dotnetcore3app\dotnetcore3app.csproj]

Now that's a good news and a bad news as well, since we can no more use any dependency service to be used in any other service configuration since it can no more be injected until the Configure() method. And so the above approach for our JWT Bearer middleware fails. The solution? Say hi to IConfigureNamedOptions interface.

The Solution - IOptions pattern:

The IOptions interface is a facet of the options pattern implementation in dotnetcore which provides an abstraction for a group of settings bound together for a single responsibility. This makes the app adhere to

We bind these settings into a single class which then can be injected into any service by means of the IOptions set of interfaces, this can delay the induction of the concerned services till the IOptions are resolved and then configures the dependent service.

Coming back to our code, let's look at the code block within the JWT middleware which is dependent on the IConfigManager service.


services.AddAuthentication(options =>
                        {
                            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                        })
                        .AddJwtBearer(options =>
                        {
                            options.TokenValidationParameters = new TokenValidationParameters()
                            {
                                ValidateAudience = true,
                                ValidateIssuer = true,
                                ValidateLifetime = true,
                                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configManager.key)),
                                ValidIssuer = configManager.Issuer,
                                ValidAudience = configManager.Audience
                            };

                            options.Events = new JwtBearerEvents()
                            {
                                OnAuthenticationFailed = (context) =>
                                {
                                    Console.WriteLine(context.Exception);
                                    return Task.CompletedTask;
                                },

                                OnMessageReceived = (context) =>
                                {
                                    return Task.CompletedTask;
                                },

                                OnTokenValidated = (context) =>
                                {
                                    return Task.CompletedTask;
                                }
                            };
                        });

The AddJwtBearer() chain method on the AddAuthentication() extension method takes in a parameter "options" which is of type JwtBearerOptions. And within this code block that we configure the JwtBearerOptions instance with the values from the Configuration by means of IConfigManager service.

We shall bind this JwtBearerOptions class into the IOptions interface where we are free to inject any dependency required. This is done as shown below:


namespace ReadersApi
{
    public class ConfigureJwtBearerOptions : IConfigureNamedOptions<JwtBearerOptions>
    {
        IConfigManager configManager;

        public ConfigureJwtBearerOptions(IConfigManager config)
        {
            this.configManager = config;
        }

        public void Configure(string name, JwtBearerOptions options)
        {
            if (name == JwtBearerDefaults.AuthenticationScheme)
            {
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidateAudience = true,
                    ValidateIssuer = true,
                    ValidateLifetime = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configManager.key)),
                    ValidIssuer = configManager.Issuer,
                    ValidAudience = configManager.Audience
                };

                options.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = (context) =>
                    {
                        Console.WriteLine(context.Exception);
                        return Task.CompletedTask;
                    },

                    OnMessageReceived = (context) =>
                    {
                        return Task.CompletedTask;
                    },

                    OnTokenValidated = (context) =>
                    {
                        return Task.CompletedTask;
                    }
                };
            }
        }

        public void Configure(JwtBearerOptions options)
        {
            Configure(JwtBearerDefaults.AuthenticationScheme, options);
        }
    }
}

The IConfigureNamedOptions interface takes in a generic Type T on which the Options pattern is applied on, and has two method overloads for Configure() which the subclass has to implement. In our implementation of the IConfigureNamedOptions interface we define it over the JwtBearerOptions and have the method Configure() have the logic to assign values to the JwtBearerOptions instance via the IConfigManager instance which can be now injected via constructor. Next, we register this NamedOptions on the service pipeline as shown below.


namespace ReadersApi
{
    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)
        {
            services.AddSingleton<IConfigManager, ConfigManager>();
	    
            // the AddJwtBearer middleware receives the JwtBearerOptions object from the IOptions during execution
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();

	    // The Class which holds the JwtBearerOptions will be returned whenever required
            services.ConfigureOptions<ConfigureJwtBearerOptions>();

            .....
        }

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

In this way we can achieve injecting dependencies into services configuration by using the IOptions set of interfaces.

Also Read:

Creating Strongly Typed configuration

Implementing JWT Bearer Authentication

Configuring and Using Middlewares