Search code examples
asp.net-core.net-core

How to refresh JwtBearer scheme metadata from discovery endpoint before application is started or just after it started?


Authentication with JwtBearer is configured as follows:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer("Bearer", options =>
    {
        Console.WriteLine("Configuring options");

        options.MetadataAddress = settings.DiscoveryUrl;
        options.AutomaticRefreshInterval = TimeSpan.FromHours(24);
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = settings.Issuer,
            ValidateAudience = true,
            ValidAudience = settings.Audience,
            ValidateLifetime = true,
        };
    });

builder.Services.AddAuthorization();

// ...

var app = builder.Build();

// ...

app.UseAuthentication();
app.UseAuthorization();

// ...

However, when application is started nothing is logged into console until first request is is sent to the app. No idea why authentication is not configured right away. The problem is that this means that metadata from discovery endpoint are fetched on first request, and because of this first request is very slow (takes like 3-5 sec). Further requests take like 100ms.

So my question is how to manually request metadata to be downloaded (and authentication scheme configured) either before application is started or just after it's started?

I tried adding following code just before application is started:

app.Lifetime.ApplicationStarted.Register(() =>
{
    var cfgManager = app.Services.GetRequiredService<IConfigurationManager<OpenIdConnectConfiguration>>();
    cfgManager.RequestRefresh();
});

But it throws

No service for type 'Microsoft.IdentityModel.Protocols.IConfigurationManager`1[Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration]' has been registered.`

I guess this is because for some reason this authentication scheme is configured after first request that uses this scheme...


Solution

  • Where did you add registration for IConfigurationManager<OpenIdConnectConfiguration> ? Cuz AddJwtBearer() wont do that for you.

    First you need to provide IConfigurationManager<OpenIdConnectConfiguration> to your JwtBearerOptions something like this :

    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer("Bearer", options =>
        {
            var configManager = new 
             ConfigurationManager<OpenIdConnectConfiguration>(
             settings.DiscoveryUrl,
             new OpenIdConnectConfigurationRetriever());
            Console.WriteLine("Configuring options");
    
            options.MetadataAddress = settings.DiscoveryUrl //You can remove this now since you have Configuration manager;
            opt.ConfigurationManager = configManager;
            options.AutomaticRefreshInterval = TimeSpan.FromHours(24);
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = settings.Issuer,
                ValidateAudience = true,
                ValidAudience = settings.Audience,
                ValidateLifetime = true,
            };
        });
    

    After that you can get the JwtBearerOptions like this: UPDATE (thanks to the @user606521)

            var app = builder.Build(); 
            using (var scope = app.Services.CreateScope()) 
            { 
              scope.ServiceProvider
             .GetRequiredService<IOptionsSnapshot<JwtBearerOptions>>().Get("Bearer")
             .ConfigurationManager!
             .GetConfigurationAsync(CancellationToken.None); 
             }