Audis.Authentication.Jwt 2.0.0

dotnet add package Audis.Authentication.Jwt --version 2.0.0
                    
NuGet\Install-Package Audis.Authentication.Jwt -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Audis.Authentication.Jwt" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Audis.Authentication.Jwt" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Audis.Authentication.Jwt" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Audis.Authentication.Jwt --version 2.0.0
                    
#r "nuget: Audis.Authentication.Jwt, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Audis.Authentication.Jwt@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Audis.Authentication.Jwt&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Audis.Authentication.Jwt&version=2.0.0
                    
Install as a Cake Tool

Audis.Authentication.Jwt

JWT-Mechanismus für Audis.Authentication. Policies, Modi und Rechte pro Client sind im Paket Audis.Authentication beschrieben.

Generische JWT-Validierung nach dem AUDIS-Token-Contract für Tokens, die eine Host-Anwendung ausstellt, in die AUDIS eingebettet ist, und die SPA als Authorization: Bearer <jwt> bzw. bei SignalR als access_token-Query sendet. Signaturprüfung und Token-Parsing übernehmen JsonWebTokenHandler und Microsoft.IdentityModel (keine eigene Signatur-Krypto, kein eigener JWT-Parser). Der Ablauf im Handler (Aussteller-Auswahl, Key-Cache, Audience, Scope-Claims) und die Prüfung der Public Keys (PEM, RSA-Größe und -Exponent, P-256-Kurvenpunkt) sind eigene Implementierungen. AUDIS-spezifische Teile (Audience audis:<tenantId>, Mapping von Mode und Knowledge) bleiben in der Anwendung.

builder.Services.AddAudisAuthentication(builder.Configuration)
    .AddJwt()
    .AddJwtAudienceResolver<TenantRouteAudienceResolver>(); // optional, Default: konfigurierte Audiences

app.MapGroup("/api/interrogation/{tenantId}")
   .RequireAudisAuth(AudisAuthPolicies.Browser);

// Claims lesen (Werte nie loggen)
var mode = AudisJwtClaims.GetMode(httpContext.User);                  // "audis:mode"
var knowledge = AudisJwtClaims.GetKnowledgeClaims(httpContext.User);  // "audis:knowledge.<id>" -> id/Wert

// Audience pro Request, z.B. aus der Route
internal sealed class TenantRouteAudienceResolver : IJwtAudienceResolver
{
    public ValueTask<IReadOnlyCollection<string>> GetAudiencesAsync(JwtAudienceContext context, CancellationToken cancellationToken) =>
        ValueTask.FromResult<IReadOnlyCollection<string>>(
            context.HttpContext.GetRouteValue("tenantId") is string tenantId ? [$"audis:{tenantId}"] : []);
}

Konfiguration (appsettings.json bzw. Key Vault):

"Authentication": {
  "Inbound": {
    "Jwt": {
      "Issuers": {
        "mpad": {
          "Issuer": "https://mpad.example.at",                 // erwarteter iss, eindeutig über alle Aussteller
          "SigningKeys": [                                     // primär: Public Keys (PEM) aus Key Vault/Konfiguration
            { "KeyId": "mpad-2026-1", "PublicKeyPem": "-----BEGIN PUBLIC KEY-----\n..." },
            { "KeyId": "mpad-2026-2", "PublicKeyPem": "..." }  // Rotation: mehrere Keys, Auswahl per kid
          ],
          // "JwksUrl": "https://mpad.example.at/.well-known/jwks.json", // alternativ zu SigningKeys, nur https
          "MaxTokenLifetimeMinutes": 120,                      // Default 60, erlaubt 1..1440
          "ClockSkewSeconds": 60,                              // Default 60, erlaubt 0..300
          // "AllowedTokenTypes": [ "at+jwt" ],                // erlaubte typ-Header, Default at+jwt (RFC 9068)
          "Audiences": [ ],                                    // optional, übersteuert die Audiences des Mechanismus
          "Policies": [ "Browser" ],                           // optional, Rechte aller Tokens dieses Ausstellers
          "Tenants": [ ]                                       // optional, leer = alle Mandanten
        }
      },
      "Audiences": [ "audis:tenant-a" ],                       // nur für den Default-Audience-Resolver
      "QueryTokenPaths": [ "/api/hub" ],                       // access_token-Query nur unter diesen Pfaden
      "KeyRefreshIntervalMinutes": 60,                         // Default 60, erlaubt 1..1440
      "UnknownKeyIdRefreshIntervalSeconds": 30                 // Default 30, erlaubt 1..3600
    },
    "Policies": {
      "Browser": { "Schemes": [ "Jwt" ], "Mode": "Enforce" }  // Default: Disabled
    }
  }
}

Token-Contract (vom Paket erzwungen):

Aspekt Vorgabe
Signatur nur RS256 oder ES256 (Allowlist). none, HS*, RS384, PS256 usw. werden abgewiesen. Symmetrische Keys werden nie verwendet, auch nicht aus JWKS oder einer eigenen Key-Quelle. RSA-Keys brauchen mindestens 2048 Bit, EC-Keys die Kurve P-256.
typ (Header) Pflicht, muss (ohne Groß-/Kleinschreibung, application/-Präfix optional) einem Wert aus AllowedTokenTypes des Ausstellers entsprechen, Default at+jwt (JWT Access Token nach RFC 9068). Fehlt typ oder ist er z.B. JWT, wird das Token abgewiesen. Damit werden ID-Tokens oder andere JWTs, die der Aussteller mit denselben Keys signiert, nie als Access Token akzeptiert. Der Aussteller muss typ: at+jwt setzen (oder einen anderen Wert, der in AllowedTokenTypes konfiguriert ist).
iss Pflicht, muss exakt (Groß-/Kleinschreibung) dem Issuer eines konfigurierten Ausstellers entsprechen. Nur dessen Keys werden verwendet.
aud Pflicht, mindestens ein Wert muss einer Audience des IJwtAudienceResolver für den aktuellen Request entsprechen.
exp, iat Pflicht. nbf wird beachtet, falls vorhanden. ClockSkewSeconds gilt für exp, nbf und iat. Alle drei werden gegen dieselbe Uhr geprüft, den registrierten TimeProvider (Default TimeProvider.System).
Lebensdauer exp - iat ≤ MaxTokenLifetimeMinutes. iat darf nicht in der Zukunft liegen (sonst ließe sich die Obergrenze umgehen).
Claims audis:mode und audis:knowledge.<id> werden unverändert übernommen (MapInboundClaims = false), AudisJwtClaims liest sie. Mapping und Whitelist sind Sache der Anwendung. audis:service, audis:policy und audis:tenant aus dem Token werden entfernt und durch den Namen des Ausstellers (audis:service, z.B. im Audit-Log EventId 1006) sowie seine Policies/Tenants ersetzt (Client-Rechte wie bei ApiKey und Hmac): Ein Token kann seine Rechte nicht selbst erweitern. Das gilt auch für einen in OnTokenValidated ersetzten Principal (mit oder ohne Success()): Jede authentifizierte Identity des endgültigen Principals erhält diese Claims.

Ablauf beim Empfang (Scheme AudisJwt):

  1. Ohne Token liefert das Scheme kein Ergebnis, andere Schemes der Policy (z.B. ApiKey) können den Request authentifizieren. Im Audit-Modus wird nur gewarnt.
  2. Das Token kommt aus dem Authorization: Bearer-Header. Nur wenn kein Authorization-Header vorhanden ist und der Pfad unter einem QueryTokenPaths-Präfix liegt (segmentweise, z.B. /api/hub), wird genau ein access_token-Query-Wert verwendet.
  3. Aussteller anhand von iss auswählen, Algorithmus prüfen, Keys des Ausstellers laden (Cache), Signatur, iss, exp/nbf mit JsonWebTokenHandler und einer pro Request neu erstellten TokenValidationParameters-Instanz prüfen.
  4. Contract-Prüfungen: aud und iat vorhanden, iat nicht in der Zukunft, Lebensdauer-Obergrenze.
  5. Audience: IJwtAudienceResolver liefert die für diesen Request erwarteten Audiences. Der Default-Resolver liest sie aus demselben Konfigurationsstand wie Aussteller und Keys, auch wenn während des Key-Ladens ein Reload erfolgt. Die Prüfung erfolgt anhand des validierten Tokens (nicht des Principals) vor OnTokenValidated: Anwendungs-Events laufen nie für ein Token einer fremden Audience, ein in OnTokenValidated ersetzter Principal beeinflusst die Prüfung nicht. Abgewiesene Tokens lösen OnAuthenticationFailed aus. Liefert der Resolver keine Audience, wird das Token abgewiesen (fail closed, Warnung EventId 3007). Wirft der Resolver eine Exception (z.B. fehlgeschlagener Tenant-Lookup), wird das Token mit 401 statt 500 abgewiesen (Fehler EventId 3009, nur Exception-Typ); nur bei abgebrochenem Request wird die Cancellation weitergereicht.
  6. Ungültige Tokens: 401 mit WWW-Authenticate: Bearer ohne Fehlerdetails.

Schlüssel:

  • Quellen: SigningKeys (PEM PUBLIC KEY mit RSA oder EC P-256, bzw. RSA PUBLIC KEY), JwksUrl oder eigene IJwtSigningKeySource-Implementierungen (AddJwtSigningKeySource<T>(), z.B. später ein Key-Vault-Key in einem eigenen Paket). Eigene Quellen werden zuerst gefragt, die erste mit Supports(issuer) == true liefert die Keys. Keys mit privatem Anteil (privater RSA-/EC-Key, Zertifikat mit Private Key) werden aus jeder Quelle verworfen (Warnung EventId 3004). Eigene Quellen und IJwtAudienceResolver erhalten die aktuellen Aussteller-Optionen (JwtIssuerContext.Options) und dürfen sie nicht verändern: Änderungen würden bis zum nächsten Reload für alle Requests gelten.
  • Cache pro Replica: Nur Public Keys, jederzeit aus der Quelle neu aufbaubar. Nach KeyRefreshIntervalMinutes wird im Hintergrund neu geladen (höchstens ein Ladevorgang pro Aussteller), Requests verwenden bis dahin die vorhandenen Keys und warten nicht auf einen langsamen JWKS-Host. Gewartet wird nur beim ersten Laden (noch keine Keys) und beim rate-limitierten Nachladen für einen unbekannten kid. Neu geladen wird nach KeyRefreshIntervalMinutes, nach einer Änderung der Key-Quelle (andere JwksUrl, geänderte SigningKeys, geänderter Issuer; andere Konfigurationsänderungen behalten den Cache) und bei einem unbekannten kid, dann höchstens einmal pro UnknownKeyIdRefreshIntervalSeconds und Aussteller (Schutz der Quelle vor Tokens mit beliebigen kids). Tokens ohne kid lösen nie ein Nachladen aus. Wartet ein Request auf Keys, endet das Warten mit dem Abbruch des Requests. Das Laden selbst läuft weiter und steht danach allen Requests zur Verfügung: Ein Client kann es durch Abbruch weder verhindern noch erneut auslösen. Eigene Key-Quellen müssen die Dauer eines Ladevorgangs selbst begrenzen (die JWKS-Quelle nach 10 s). Schlägt das Laden fehl, bleiben die zuletzt geladenen Keys derselben Key-Quelle aktiv, Keys einer vorherigen Quelle werden nie weiterverwendet (Warnung EventId 3002, nur Exception-Typ). Liefert die Quelle erfolgreich, aber ohne unterstützten Key (z.B. leeres JWKS, nur use: enc oder unbrauchbare Keys wie ein RSA-Exponent kleiner 3 bzw. gerade oder ein EC-Punkt, der nicht auf P-256 liegt), gilt das ebenfalls als Fehlschlag (Warnung EventId 3010). In beiden Fällen wird nach UnknownKeyIdRefreshIntervalSeconds erneut geladen. Ein per Reload geändertes KeyRefreshIntervalMinutes gilt sofort, auch für bereits geladene Keys.
  • JWKS: Download über den HttpClient Audis.Authentication.Jwt.Jwks (Timeout 10 s, max. 512 KB, keine Redirects). http ist nur für Loopback-Hosts mit AllowInsecureLoopbackJwksUrl: true erlaubt. Keys mit privatem Anteil, use ≠ sig oder kty oct werden ignoriert (Warnung EventId 3004).
  • Rotation: Neuen Public Key mit neuem KeyId zusätzlich eintragen, Aussteller auf den neuen Key umstellen, alten Key nach Ablauf der maximalen Token-Lebensdauer entfernen. Konfigurations-Reloads (Key Vault) wirken ohne Neustart. Ein aus einem JWKS entfernter Key wird pro Replica bis zum nächsten Refresh weiter akzeptiert (höchstens KeyRefreshIntervalMinutes). Für einen sofortigen Widerruf KeyRefreshIntervalMinutes per Reload kurzzeitig senken (gilt sofort, auch für bereits geladene Keys). Ist die Konfiguration nach einem Reload ungültig, bleibt die letzte gültige aktiv (Fehler EventId 1002 bzw. 1003, ohne Key-Material, siehe Betrieb).

Validierung (Start und Reload): fehlender oder doppelter Issuer, keine oder zwei Key-Quellen, SigningKeys ohne oder mit doppeltem KeyId, PEM mit privatem Schlüssel, Zertifikat oder ungültigem Inhalt, RSA < 2048 Bit, EC-Kurve ≠ P-256, JwksUrl nicht absolut oder ohne https, Werte außerhalb der erlaubten Bereiche, leere AllowedTokenTypes bzw. leere Werte darin, ungültige QueryTokenPaths (z.B. /; ein abschließendes / wie in /api/hub/ ist erlaubt und wird ignoriert), keine Audience bei Default-Resolver, unbekannte Policies oder ungültige Tenants eines Ausstellers sowie eine aktive Policy mit Jwt ohne Aussteller verhindern den Start. Bei einem Reload der Policies (z.B. Umstellung von ApiKey auf Jwt) werden Aussteller und JWT-Settings desselben Reloads mitgeprüft: Ohne Aussteller oder mit ungültigen Settings (z.B. ungültigem PEM) wird der Reload nicht übernommen, die zuletzt gültigen Policies bleiben aktiv. Fehlermeldungen enthalten nie Key-Material.

Betrieb (Konfigurations-Reloads, z.B. Key Vault):

  • Reload ganz oder gar nicht: Ein Reload wird nur übernommen, wenn die gesamte Konfiguration gültig ist. Enthält sie irgendeinen Fehler, auch bei einem anderen Aussteller oder einer Audience, bleibt die zuletzt gültige Konfiguration vollständig aktiv. Dann gelten auch entfernte bzw. widerrufene Keys und Aussteller weiter. Das ist bewusst so: Ein Tippfehler soll nicht alle Tokens abweisen (Totalausfall der Leitstelle). Geloggt wird auf Error mit EventId 1002 (Validierungsfehler) bzw. 1003 (Konfiguration nicht bindbar, nur Exception-Typ), jeweils mit dem Hinweis, dass keine Änderung dieses Reloads wirksam ist.
  • Vor dem Rollout validieren: Konfigurationsänderungen (insbesondere in Key Vault) vor dem Ausrollen prüfen, z.B. durch Start einer Instanz mit der neuen Konfiguration in Staging.
  • Key-Widerruf in den Logs verifizieren: Nach dem Entfernen eines kompromittierten Keys oder Ausstellers auf allen Replicas prüfen, dass kein EventId 1002/1003 geloggt wurde. Empfehlung: Alert auf diese EventIds.
  • Neustart: Mit ungültiger Konfiguration schlägt der Start fehl (ValidateOnStart). Eine neu gestartete Replica läuft also nie mit einer alten Konfiguration weiter.
  • Replicas: Jede Replica lädt die Konfiguration unabhängig (Key-Vault-Refresh-Intervall, File Watcher). Bis alle Replicas neu geladen haben, kann ein entfernter Key auf einem Teil der Replicas noch gelten. Für einen sofortigen Widerruf die Replicas neu starten (Rolling Restart).

Sicherheitshinweise:

  • Statelessness: Validierung allein aus Token und Public Key. Kein serverseitiger Token-Cache, keine Session, SaveToken = false. Jede Replica kann jeden Request validieren.
  • Logging: Token, access_token-Wert und Claim-Werte werden vom Paket nie geloggt. Fehlermeldungen sind feste Texte, IdentityModel-Meldungen (die bei IdentityModelEventSource.ShowPII = true das Token enthalten können) werden nicht weitergereicht. Abgewiesene Tokens werden mit Grund auf Debug geloggt (EventId 3006).
  • Request-Logs des Hosts: ASP.NET Core loggt in Microsoft.AspNetCore.Hosting.Diagnostics (Information, „Request starting/finished“) die vollständige URL inklusive access_token-Query. Das liegt außerhalb des Pakets: Die Anwendung muss diese Kategorie auf Warning setzen oder die Query maskieren, ebenso bei HttpLogging/W3C-Logging, Reverse Proxies und APM.
  • JwtBearerOptions: Token-Handler, Claim-Mapping, SaveToken, IncludeErrorDetails und Authority/Metadata des Schemes AudisJwt werden per Post-Configure festgelegt und überschreiben Einstellungen der Anwendung; eigene Configure-Aufrufe für diese Werte sind wirkungslos und werden nicht unterstützt. Eigene Events bzw. EventsType bleiben erhalten. Die access_token-Query liest der Handler selbst, sie funktioniert daher auch mit EventsType; OnMessageReceived läuft danach und kann das Token ersetzen. Die Audience-Prüfung liegt im Handler und ist von den Events nicht betroffen.
  • Mehrere Modes im Token: AudisJwtClaims.GetMode wirft eine InvalidOperationException, wenn das Token mehrere unterschiedliche audis:mode-Werte enthält. Claims anderer Schemes werden von AudisJwtClaims ignoriert.

Lizenz

MIT, © softaware gmbh.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
2.0.0 121 11/4/2026