dotnet / dotnet/AspNetCore.Docs

Add guidance for safely implementing a custom IssuerSigningKeyResolver (multi-tenant / JWKS scenarios)

Open
#37,076 2 comments 1 reaction 1 assignee Claimed by @tdykstra View on GitHub
Dominant language
C#
Stars
13.1k
Forks
24.6k
Avg merge
1d 3h
Merged PRs (30d)
97

Description

## Help us make content visible

**Search terms used:** `IssuerSigningKeyResolver aspnetcore`, `JwtBearer custom signing key`, `multi-tenant JWT validation aspnetcore`.

**Docs found that didn't address the concern:**
- https://learn.microsoft.com/aspnet/core/security/authentication/configure-jwt-bearer-authentication — covers `Authority`/`IssuerSigningKey` but not custom resolvers.
- An audit of `dotnet/AspNetCore.Docs` returns **0 hits** for `IssuerSigningKeyResolver`. The same query on `dotnet/aspnetcore` also returns 0. The only hits across the `dotnet` org are inside the IdentityModel implementation itself — there is no narrative guidance on how to implement this extension point safely.

## Describe the new topic

**Why it's needed**

`TokenValidationParameters.IssuerSigningKeyResolver` is the standard extension point for dynamic signing-key lookup (multi-tenant apps, JWKS caching, custom key stores, key-rotation rollouts). It's widely used in real-world apps but has no official sample. Developers searching for examples land on third-party blogs and Stack Overflow, where anti-patterns are common.

The risk is concrete: ASP.NET Core's default JwtBearer config defends against HS/RS algorithm-confusion attacks via *typed-key resolution* — `JsonWebTokenHandler` only verifies HS256 signatures with `SymmetricSecurityKey` candidates. A custom resolver that returns a `SymmetricSecurityKey` derived from public-key bytes (a plausible mistake when caching key material in a multi-tenant scenario) silently disables that defense. The same forged HS256 token that the default config rejects with 401 then validates with 200 OK and full claim assumption.

I have a self-contained reproduction (Minimal API + xUnit, runs `dotnet test` in under 2 seconds) demonstrating both the safe default and the broken-resolver attack:
**https://github.com/loganbryanx/aspnetcore-auth-lab**

**Suggested ToC location**

`fundamentals/security/authentication/configure-jwt-bearer-authentication` — as a new sibling page or expanded section titled "Custom signing-key resolution".

**Abstract**

A how-to for implementing `IssuerSigningKeyResolver` in scenarios that aren't covered by `Authority`-based discovery (multi-tenant apps, custom key stores, advanced JWKS caching). Side-by-side examples show a safe resolver that asserts key/algorithm compatibility before yielding keys, and an anti-pattern resolver that silently disables the framework's typed-key defense against HS/RS algorithm confusion. Includes the recommendation to set `ValidAlgorithms` as defense-in-depth.

**Outline**

1. When you need a custom resolver (and when you don't — prefer `Authority` if possible)
2. The safe pattern — minimal multi-tenant resolver, with `kid`-keyed lookup
3. JWKS caching with `IssuerSigningKeyResolverUsingConfiguration`
4. The anti-pattern — wrapping public-key bytes in `SymmetricSecurityKey`, with the algorithm-confusion risk it introduces
5. Defense in depth — setting `ValidAlgorithms`, asserting key types, logging on validation failure
6. Testing your resolver — a forged-token regression test pattern

Happy to draft the PR if the team agrees the gap warrants it.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.