Programming

Resolve JWT Signing Key with DI in ASP.NET Core JWT Bearer

Learn how to configure ASP.NET Core JWT Bearer authentication to resolve issuer signing keys dynamically from ISigningKeyRepository using dependency injection and IssuerSigningKeyResolver at runtime.

1 answer 1 view

How to resolve issuer signing key using dependency injection from ISigningKeyRepository in AddJwtBearer configuration in ASP.NET Core?

I want to authenticate users with JWT tokens using an ISigningKeyRepository that handles rotating and creating signing keys in the database. When registering JWT authentication in IServiceCollection with this code:

csharp
.AddJwtBearer("UserJwtAuthentication", options => {
 options.TokenValidationParameters = new TokenValidationParameters {
 ValidIssuer = "test",
 ValidAudience = "test",
 ValidateIssuer = true,
 ValidateAudience = true,
 ValidateIssuerSigningKey = true,
 };
});

I need to pass the key via IssuerSigningKeyResolver, but the key is resolved using ISigningKeyRepository, which cannot be accessed directly in the IServiceCollection configuration context.

Is there a way to configure JWT Bearer authentication to resolve the signing key from ISigningKeyRepository at runtime using dependency injection?

Yes, you can resolve the issuer signing key dynamically from your ISigningKeyRepository in ASP.NET Core JWT Bearer authentication by setting a custom IssuerSigningKeyResolver delegate in the AddJwtBearer options. This resolver executes at runtime during each token validation, giving you access to the current HttpContext and its RequestServices for dependency injection—no need to hack the startup configuration lambda. Just register your repository as scoped, and pull it from the validation context to fetch the key by kid from the JWT header.

Contents



Why Direct DI Fails in AddJwtBearer


Ever tried grabbing a service like ISigningKeyRepository inside the AddJwtBearer lambda? It won’t work. That configuration runs during IServiceCollection setup, before the container is built and scoped services like your repo are available. Building a temporary ServiceProvider with services.BuildServiceProvider() might seem tempting—some Stack Overflow answers suggest it for quick key loads—but it creates duplicate singletons and skips proper lifetimes.

Worse, for rotating keys stored in a database, you need runtime access anyway. Static keys? Fine for dev. But production with key rotation demands something dynamic. That’s where JWT signing key resolvers shine. They kick in per request, post-container build.


The Power of IssuerSigningKeyResolver


The IssuerSigningKeyResolver is your runtime hero for JWT Bearer validation. It’s a delegate invoked by JwtSecurityTokenHandler every time a token hits your app. Signature:

csharp
Func<string, SecurityToken, TokenValidationParameters, ValidationContext, ResolveSigningKeyResult?>

It gets the raw token, parsed SecurityToken, validation params, and crucially—a ValidationContext with HttpContextAccessor-like access to services. No more config-time struggles.

According to deep dives into the handler’s source, if unset, it falls back to metadata discovery. But set it custom, and you control key lookup by kid (key ID) or x5t thumbprint from the JWT header. Perfect for your repo pulling from DB.

Why does this matter? Token validation happens in middleware, fully scoped. Inject away.


Implementing Key Resolution with DI


Here’s the money shot. In Program.cs or Startup.ConfigureServices:

csharp
builder.Services.AddScoped<ISigningKeyRepository, SigningKeyRepository>();

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
 .AddJwtBearer("UserJwtAuthentication", options =>
 {
 options.TokenValidationParameters = new TokenValidationParameters
 {
 ValidIssuer = "test",
 ValidAudience = "test",
 ValidateIssuer = true,
 ValidateAudience = true,
 ValidateIssuerSigningKey = true,
 // No IssuerSigningKey here—resolver handles it
 };

 options.Events = new JwtBearerEvents
 {
 OnTokenValidated = context =>
 {
 // Optional: Post-validation logic
 return Task.CompletedTask;
 }
 };

 // The resolver magic
 options.TokenValidationParameters.IssuerSigningKeyResolver =
 (tokenString, securityToken, validationParameters, validationContext) =>
 {
 // DI via RequestServices—scoped, fresh per request
 var repo = validationContext.HttpContext.RequestServices
 .GetRequiredService<ISigningKeyRepository>();

 if (securityToken is JwtSecurityToken jwtToken)
 {
 var kid = jwtToken.Header.Kid; // Or x5t
 var key = repo.GetSigningKeyAsync(kid).GetAwaiter().GetResult(); // Sync for simplicity; async possible with custom handler

 if (key != null)
 {
 return new[] { new SymmetricSecurityKey(key.KeyBytes) }; // Or RsaSecurityKey
 }
 }

 return null; // Invalid key → fail auth
 };
 });

Boom. Your repo resolves the JWT signing key by kid at validation time. GetAwaiter().GetResult() works for sync delegate; for full async, extend JwtBearerHandler. Test with a rotating key—issue a token with new kid, watch it validate seamlessly.

But what if no kid matches? Resolver returns null, auth fails. Graceful.


Registering Services and Middleware


Don’t forget the basics. Your ISigningKeyRepository must be scoped (per-request lifetime) to hit the DB safely.

csharp
// Program.cs (.NET 6+)
builder.Services.AddScoped<ISigningKeyRepository, YourRepo>();

builder.Services.AddAuthorization();
app.UseAuthentication();
app.UseAuthorization();

For minimal APIs or controllers, that’s it. In controllers, [Authorize(AuthenticationSchemes = "UserJwtAuthentication")] triggers it.

Pro tip: Cache resolved keys in repo with IMemoryCache to dodge DB thrash. services.AddMemoryCache(); then cache.GetOrCreate(keyId, entry => LoadFromDbAsync(keyId));.


Alternatives for Static or Hybrid Keys


Resolver too heavy? For fixed keys, use IOptions<JwtBearerOptions>.Configure:

csharp
services.AddSingleton<ISigningKeyRepository>(...); // Singleton for static

services.AddOptions<JwtBearerOptions>("UserJwtAuthentication")
 .Configure<ISigningKeyRepository>((options, repo) =>
 {
 var key = repo.GetCurrentKey(); // Startup-time fetch
 options.TokenValidationParameters.IssuerSigningKey = key;
 });

Or IPostConfigureOptions<JwtBearerOptions> for tweaks post-registration. GitHub issues from the ASP.NET team highlight these for edge cases, but resolver wins for dynamics.

Hybrid? Resolver first, fallback to static. Flexibility rules.


Security Tips and Pitfalls


Key rotation exposes risks. Always validate kid against allowlist. Cache? Set sliding expiration matching rotation interval. DB perf? Async all the way, or you’ll block middleware.

Pitfalls: Sync-over-async deadlocks—use .Result sparingly. No HttpContext outside requests? Resolver skips gracefully. Logs? Hook OnAuthenticationFailed.

Stuck on invalid signatures? Check clock skew (ClockSkew = TimeSpan.Zero), audience mismatches. Tools like jwt.io decode headers fast.


Sources


  1. How Do ASP.NET Core Services Validate JWT Signature Signed by AAD? — Details IssuerSigningKeyResolver mechanics and invocation: https://zhiliaxu.github.io/how-do-aspnet-core-services-validate-jwt-signature-signed-by-aad.html
  2. Better way to get the Json Web Key in AddJwtBearer — Official discussion on DI access limitations and resolver proposals: https://github.com/dotnet/aspnetcore/issues/21491
  3. Jwt Bearer and dependency injection — Code example of early ServiceProvider pitfalls in JWT config: https://stackoverflow.com/questions/61186836/jwt-bearer-and-dependency-injection
  4. ASP.NET Core 3.1 JWT signature invalid when using AddJwtBearer() — IOptions.Configure pattern for injecting keys via DI: https://stackoverflow.com/questions/60434420/asp-net-core-3-1-jwt-signature-invalid-when-using-addjwtbearer
  5. Using dependency injection to replace JWT Bearer Options — PostConfigureOptions for runtime JWT options tweaks: https://stackoverflow.com/questions/51586562/using-dependency-injection-to-replace-jwt-bearer-options-in-asp-net-core

Conclusion


Configuring ASP.NET Core JWT Bearer to pull signing keys from ISigningKeyRepository boils down to IssuerSigningKeyResolver—runtime DI via validationContext.HttpContext.RequestServices. It’s secure, scalable for rotation, and sidesteps startup hacks. Start with the code above, add caching, and you’re golden. Questions on async tweaks or AAD hybrids? The resolver scales there too.

Authors
Verified by moderation
Moderation
Resolve JWT Signing Key with DI in ASP.NET Core JWT Bearer