Update JwtBearerOptions via DI after configuration when secret or issuer changes

See original GitHub issue

Similar to Better way to get the Json Web Key in AddJwtBearer and Use object in DI when AddJwtBearer, I wanted a way to have my Jwt secret/issuer come from a configuration source that could be updated and have our sites/apis resilient enough to automatically use them if they changed without requiring a restart of each site that depended on the credentials.

I wanted to follow up with this question on either of those tasks but both were closed. I don’t know if @HaoK wants to weigh in a bit since his ‘tip’ is what got Voccano’s code working in issue #21491, but the issue is, for some reason DI for TheKeep (my object) was successfully injected, but the exposed properties were not updated at the time of my UpdatableJwtBearerOptions class. When it was ultimately injected into the minimal api endpoint call, they WERE updated there.

The relevant code is below, and I’ll post screen shots after that represent:

  1. Start api with issuer set to ‘bad.*’
  2. Make a request with ‘bad.*’ and receive 401 unauthorized (correct).
  3. Update configuration source to remove ‘bad.’
  4. Make a request and receive 200 (correct)
  5. First screen will be debug inside UpdatableJwtBearerOptions and second will be inside minimal api endpoint.

Anything obvious as to why TheKeep wouldn’t work?

Configure JwtBearerOptions and ‘TheKeep’ (my configuration class)

builder.Services
	.Configure<TheKeepSettings>( builder.Configuration.GetSection( "TheKeep" ) ) // configuration in here
	.AddScoped<Domain.Configuration.TheKeep>()
builder.Services.AddTransient<IConfigureOptions<JwtBearerOptions>, UpdatableJwtBearerOptions>();

TheKeep snippet showing DI of ‘IConfiguration’ and Jwt property

public TheKeep( IOptionsSnapshot<TheKeepSettings> theKeepSettings )
{
	this.Jwt = theKeepSettings.Value.Jwt;
}
public Jwt Jwt { get; init; }

UpdatableJwtBearerOptions Class implementation to set Jwt secret/issuer

public class UpdatableJwtBearerOptions : IConfigureNamedOptions<JwtBearerOptions>
{
	private readonly IConfiguration configuration;
	private readonly Domain.Configuration.TheKeep theKeep;

	public UpdatableJwtBearerOptions( IConfiguration configuration, Domain.Configuration.TheKeep theKeep )
	{
		this.configuration = configuration;
		this.theKeep = theKeep;
	}

	public void Configure( string? name, JwtBearerOptions options )
	{
		options.TokenValidationParameters.IssuerSigningKeyResolver = ( token, securityToken, kid, validationParameters ) =>
		{
			validationParameters.ValidIssuer = configuration.GetValue<string>( "TheKeep:Jwt:WebServiceProxy:Issuer" );
			var issuerFromTheKeep = theKeep.Jwt.WebServiceProxy.Issuer;

			return new List<SecurityKey>() 
			{ 
				new SymmetricSecurityKey( Encoding.ASCII.GetBytes( configuration.GetValue<string>( "TheKeep:Jwt:WebServiceProxy:Secret" )! ) )
			};
		};
	}

	public void Configure( JwtBearerOptions options ) => throw new NotImplementedException();
}

Snippet of minimal api…

public override async Task HandleAsync( NexgenRequest r, CancellationToken c )
{
	var issuerFromTheKeep = theKeep.Jwt.WebServiceProxy.Issuer;
	// ... code omitted
}

TheKeep not updated yet… image

TheKeep updated… image

Issue Analytics

  • State:open
  • Created 2 months ago
  • Comments:13 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
halter73commented, Aug 12, 2023

If you want to continue using TheKeep, I would probably just make it a Singleton and change the property to query the IOptionsMonitor in the getter.

public Jwt Jwt => _theKeepSettings.Value.Jwt
0reactions
terryaneycommented, Aug 11, 2023

@halter73 Thanks for the ideas. So I didn’t provide enough code I don’t think 😃 But maybe you already knew what I was doing.

Here is my builder registration

builder.Services.AddTransient<IConfigureOptions<JwtBearerOptions>>( provider =>
{
	var configuration = provider.GetRequiredService<IConfiguration>();
	using( var scope = provider.CreateScope() )
	{
		var theKeep = scope.ServiceProvider.GetRequiredService<TheKeep>();
		var options = new UpdatableJwtBearerOptions( configuration, theKeep );
		return options;
	}
});

And from above, my constructor for UpdatableJwtBearerOptions is:

public UpdatableJwtBearerOptions( IConfiguration configuration, Domain.Configuration.TheKeep theKeep )
{
	this.configuration = configuration;
	this.theKeep = theKeep;
}

As is, you were right.

  1. I only hit Configure one time, but hit the IssuerSigningKeyResolver delegate every request.
  2. Inside the delegate, if I modified my *.json file, the configuration.GetValue<string>() was updated, but theKeep value wasn’t.
  3. I put a breakpoint in the ctor for UpdatableJwtBearerOptions and as you probably know, it was only hit once, which surprised me since it was registered as Transient. So obviously, my reference to theKeep was never updated.

I put in your builder.Services.AddSingleton<IOptionsChangeTokenSource<JwtBearerOptions>> suggestion and:

  1. The Configure method was triggered as soon as there was an edit to the file which simply re-assigned the IssuerSigningKeyResolver delegate.
  2. The ctor was not called, so I was stuck with the old theKeep reference.

So currently, I simply work with an IConfiguration object inside UpdatableJwtBearerOptions, don’t need to resolve a scoped TheKeep to pass in, and not using your ‘TokenSource’ suggestion. The only downside to this is accessing the configuration via GetValue<string>( path ) instead of strongly typed properties of TheKeep.

If you see anything that might allow me to use TheKeep, let me know.

Read more comments on GitHub >

github_iconTop Results From Across the Web

ASP.NET Core - change JWT SecurityKey during runtime
There is a delegate IssuerSigningKeyResolver , in the TokenValidationParameters, that you can set while configuring the other options.
Read more >
Protected web API: Code configuration
Learn how to build a protected web API and configure your application's code.
Read more >
What is JWT and how to add it to ASP.NET Core
In-addition, we can add a signing key which is a secret that can be used to verify an access token. As these values...
Read more >
Creating And Validating JWT Tokens In C# .NET
I'm trying to validate a token created using Microsoft.Identity.Client in a mobile app. The token is passed in a header to a REST...
Read more >
A look behind the JWT bearer authentication middleware in ...
In this post we look at the JwtBearerAuthenticationMiddleware as a means to understanding authentication in ASP.NET Core in general.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found