SafeWebCore.JwtBearer
1.0.0
dotnet add package SafeWebCore.JwtBearer --version 1.0.0
NuGet\Install-Package SafeWebCore.JwtBearer -Version 1.0.0
<PackageReference Include="SafeWebCore.JwtBearer" Version="1.0.0" />
<PackageVersion Include="SafeWebCore.JwtBearer" Version="1.0.0" />
<PackageReference Include="SafeWebCore.JwtBearer" />
paket add SafeWebCore.JwtBearer --version 1.0.0
#r "nuget: SafeWebCore.JwtBearer, 1.0.0"
#:package SafeWebCore.JwtBearer@1.0.0
#addin nuget:?package=SafeWebCore.JwtBearer&version=1.0.0
#tool nuget:?package=SafeWebCore.JwtBearer&version=1.0.0
SafeWebCore.JwtBearer
Optional companion module for SafeWebCore that makes a misconfigured or unreachable JWT authority fail loud before your users ever see a 401 — and optionally hardens your token validation rules.
This module exists because of a real issue reported by Stephan van Rooij (@svrooij): dotnet/aspnetcore#67991 — "Setup JwtBearer authentication with faulty authority should crash".
If a security feature is misconfigured, the application should fail fast at startup instead of starting normally while silently returning 401 for everything.
The .NET team only scheduled the fix for .NET 12 Planning (milestone). This module gives you the behavior today, on .NET 10, plus additional opt-in JWT hardening.
The problem (Stephan's repro)
A misspelled authority (organisations instead of organizations) makes the app start fine
but return 401 invalid_token for every request — while logging the root cause only at
Information level, below the default Microsoft.AspNetCore: Warning filter. So: an app that
runs green, logs nothing, and 401s everything. Requests fail closed (no bypass), but the
loudness is missing.
With SafeWebCore.JwtBearer, that same app either refuses to start (fail fast) or starts with a loud Error log, depending on your preference.
Unhandled exception. System.InvalidOperationException: The JWT authority for scheme 'Bearer'
is misconfigured (HTTP 4xx from 'https://login.microsoftonline.com/organisations/v2.0').
Fix the Authority/MetadataAddress before starting.
Install
dotnet add package SafeWebCore.JwtBearer
Quick start
Option 1 — Startup validation only (Stephan's fix)
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => { /* your authority, issuer, audience, ... */ });
// Fail fast at startup when the authority is misconfigured (HTTP 4xx):
builder.Services.AddJwtBearerAuthorityValidation(o => o.FailFast = true);
// Or fail loud but keep the app running (requests still fail closed):
// builder.Services.AddJwtBearerAuthorityValidation(); // FailFast defaults to false
Option 2 — Startup validation + token hardening (recommended)
builder.Services.AddJwtBearerHardening(o =>
{
o.ValidateAudience = true;
o.ValidAudiences.Add("api://my-api");
o.AllowedAlgorithms.Add("RS256");
o.MaximumTokenLifetime = TimeSpan.FromHours(1);
});
Hardening is applied after your AddJwtBearer configuration and can only increase strictness;
stronger settings you already defined are never weakened. It also registers the startup guard with
FailFast = true as a secure default.
Option 3 — Everything in one line
builder.Services.AddSafeWebCoreJwtBearer(
options =>
{
options.Authority = "https://login.microsoftonline.com/organizations/v2.0";
options.TokenValidationParameters.ValidAudiences = new[]
{
"api://98f7735b-23c5-4b12-bc16-0e2f3a5d7e21",
};
},
hardening => hardening.MaximumTokenLifetime = TimeSpan.FromHours(1));
Startup validation
| Option | Default | Description |
|---|---|---|
Scheme |
Bearer |
The JWT bearer authentication scheme to validate. |
FailFast |
false |
When true, a permanent (HTTP 4xx) authority failure throws at startup. |
EnforceStaticConfigurationChecks |
true |
Runs deterministic, network-free checks: absolute HTTPS authority (when RequireHttpsMetadata is on), audience must be configured when validation is enabled, issuer required when no metadata resolves it, none algorithm rejected. |
RequireSigningKeys |
true |
A discovery document that loads but contains no signing keys (empty JWKS) is a permanent configuration error: Error log and (with FailFast) a startup throw. Set to false to only log a Warning. |
PeriodicValidationInterval |
null |
When set, the authority metadata is re-validated on this interval while the app runs. Periodic failures never throw — permanent ones log Error, transient ones Warning — so a running server is never killed by a health check. |
4xx is permanent (typo, unknown tenant, revoked metadata) → Error log and, with FailFast,
a startup crash. Everything else (5xx, timeout, DNS) is transient → Warning log, the app
starts, and requests fail closed (401) until the identity provider is reachable.
A 200 response without signing keys (empty JWKS) is permanent too: logged at Error and, with
FailFast, a startup throw. Set RequireSigningKeys = false to downgrade it to a Warning.
Setting PeriodicValidationInterval re-probes the endpoint while the app runs — important because
IdentityModel's last-known-good cache otherwise masks metadata failures once a healthy configuration
was loaded.
Token hardening (JwtBearerHardeningOptions)
| Option | Default | Enforces |
|---|---|---|
RequireSignedTokens |
true |
Tokens must be signed (alg: none rejected). |
RequireExpirationTime |
true |
An exp claim must be present. |
ValidateLifetime |
true |
nbf/exp ranges are validated. |
RequireTokenType |
true |
The typ header must be present and match AllowedTokenTypes (JWT, at+jwt). |
RequireJwtId |
false |
A unique jti (JWT ID) claim is required. |
RequireNotBefore |
false |
An nbf claim is required. |
RequireIssuedAt |
false |
An iat claim is required. |
MaximumClockSkew |
5 min |
Clock skew is capped at this value (the tighter of existing and configured wins). |
MaximumTokenLifetime |
null |
Tokens whose valid lifetime (exp - nbf) exceeds this are rejected. Common: 1 hour. |
AllowedAlgorithms |
(empty) | When non-empty, only the listed JWS algorithms are accepted (RS256, ES256, PS256, ...). |
ValidateIssuer / ValidIssuers |
false / (empty) |
Enforces issuer validation against the configured issuer(s). |
ValidateAudience / ValidAudiences |
false / (empty) |
Enforces audience validation against the configured audience(s). |
EnableRuntimeMetadataLogging |
true |
Wraps the configuration manager so metadata failures surf at Error (4xx) / Warning also during runtime, not just at startup. |
Runtime metadata logging
.NET/IdentityModel only logs OpenID Connect metadata retrieval failures at Information level.
This module wraps the configuration manager (via IPostConfigureOptions) so that a 4xx metadata
failure during runtime is logged at Error and a transient failure at Warning, rate-limited to
one line per refresh interval. Requires AddJwtBearerHardening/AddSafeWebCoreJwtBearer to run
after AddJwtBearer.
How it works
JwtAuthorityValidationGuard(anIHostedService, registered at startup):- runs the deterministic configuration checks, then eagerly fetches the OpenID Connect metadata
(issuer + signing keys) for your scheme's
IConfigurationManager; - HTTP 4xx → permanent →
Error(+ fail fast when configured); - anything else → transient →
Warning, app starts; - success →
Information. Nothing else changes.
- runs the deterministic configuration checks, then eagerly fetches the OpenID Connect metadata
(issuer + signing keys) for your scheme's
JwtBearerHardeningApplicationapplies the token-validation defaults and chains claim checks (typ,jti,nbf,iat, maximum lifetime) onto your existingOnTokenValidatedhandler.LoggingConfigurationManagerdecorates the manager so metadata failures that actually surface (no usable cached configuration) are logged atError/Warningduring runtime, not just at startup.
Reproduce it yourself
The repository includes examples/JwtBearerDemo — a copy of
Stephan's exact reproduction (misspelled organisations authority, his exact token-validation
settings) with a --broken switch between the broken and the fixed behavior.
Limitations
- The guard validates the authority once at startup; requests keep failing closed (401) whenever
tokens cannot be validated. The runtime metadata logging (Option 2/3) reports retrieval failures
that surface — once IdentityModel has loaded a healthy configuration, its last-known-good
behavior serves the cached metadata without throwing. To actively detect an identity-provider
outage after a healthy start, set
PeriodicValidationInterval(background re-validation that never throws; permanent failures log atError, transient atWarning). - The metadata is fetched one extra time at startup (which also warms the configuration cache).
AddJwtBearerHardeningmust be registered afterAddJwtBearer.
License
MIT, same as SafeWebCore.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.11)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 0 | 9/10/2026 |