Abblix.JWT
2.4.0
Prefix Reserved
dotnet add package Abblix.JWT --version 2.4.0
NuGet\Install-Package Abblix.JWT -Version 2.4.0
<PackageReference Include="Abblix.JWT" Version="2.4.0" />
<PackageVersion Include="Abblix.JWT" Version="2.4.0" />
<PackageReference Include="Abblix.JWT" />
paket add Abblix.JWT --version 2.4.0
#r "nuget: Abblix.JWT, 2.4.0"
#:package Abblix.JWT@2.4.0
#addin nuget:?package=Abblix.JWT&version=2.4.0
#tool nuget:?package=Abblix.JWT&version=2.4.0
Abblix.JWT
A JWT and JOSE toolkit for .NET, built entirely on the platform's cryptographic primitives and System.Text.Json.Nodes - no dependency on Microsoft.IdentityModel.Tokens. It implements JWS (RFC 7515), JWE (RFC 7516), JWK (RFC 7517) and JWA (RFC 7518), and is the token layer behind Abblix OIDC Server, usable on its own.
What's New in Version 2.4
🚀 Features
- Complete JWE key management: every key-management algorithm of RFC 7518 section 4, including AES key wrapping (RFC 3394) and opt-in password-based encryption with a bounded work factor
- External signing keys: a custodian seam through which the private half of a key is held outside the process, served by Abblix.JWT.Vault for HashiCorp Vault and OpenBao Transit and Abblix.JWT.Azure for Azure Key Vault
- Replay prevention for single-use tokens, with one store serving every token profile
✏️ Improvements
- Clock tolerance travels as one value with separate past and future halves, so a window such as the one FAPI 2.0 prescribes is expressed exactly
- A NumericDate is read as RFC 7519 section 2 defines it: a fractional or exponent-written number is a date, and a claim that cannot be read is refused by name rather than thrown
- The signed discovery document uses the standard algorithm for a key that declares none (RFC 7517 section 4.4)
Install
dotnet add package Abblix.JWT
Issue and validate a token
services.AddJsonWebTokens();
The registration provides IJsonWebTokenCreator and IJsonWebTokenValidator. Issuing signs the token with the key you pass, and encrypts it when you also pass an encryption key:
var key = JsonWebKeyFactory.CreateRsa(PublicKeyUsages.Signature, SigningAlgorithms.RS256);
var jwt = await creator.IssueAsync(
new JsonWebToken
{
Header = { Algorithm = SigningAlgorithms.RS256 },
Payload =
{
Issuer = "https://issuer.example.com",
Audiences = ["https://api.example.com"],
ExpiresAt = DateTimeOffset.UtcNow.AddMinutes(5),
},
},
key);
Validation returns a verdict rather than throwing on a bad token: the result is a Result carrying either the parsed token or the error naming what failed - malformed input, unknown algorithm, bad signature, unmet issuer or audience policy - and the parameters carry the checks as delegates, so issuer trust and key resolution stay the caller's policy.
Two classes of failure still surface as exceptions, both after the signature has been verified: an exp/nbf value that is not a number or falls outside DateTimeOffset's range, and a resolved key carrying no public material - wrap the call if your endpoint must answer every input with a protocol error:
var result = await validator.ValidateAsync(jwt, new ValidationParameters
{
ValidateIssuer = issuer => Task.FromResult(issuer == "https://issuer.example.com"),
ValidateAudience = audiences => Task.FromResult(audiences.Contains("https://api.example.com")),
ResolveIssuerSigningKeys = issuer => KnownKeysOf(issuer),
});
The payload is a JsonObject underneath, so claims keep their JSON types - numbers, arrays and nested objects need no string round-trips, and custom claims are first-class.
Algorithms
- Signing: RS256/RS384/RS512, PS256/PS384/PS512, ES256/ES384/ES512, HS256/HS384/HS512.
- Key management: RSA-OAEP, RSA-OAEP-256, AES-GCM key wrapping (A128GCMKW/A192GCMKW/A256GCMKW), direct encryption (dir).
- Content encryption: A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM.
Two further key-management families ship and stay off until a host asks for them, each because of a
cost the default should not impose. AddRsaPkcs1KeyManagement() enables RSA1_5. AddPbes2KeyManagement()
enables PBES2-HS256+A128KW, PBES2-HS384+A192KW and PBES2-HS512+A256KW, where the inbound token's p2c
header dictates PBKDF2 work before the token has been authenticated; the iteration count is bounded to
[1000, 10000] even once enabled. Interop with a partner that requires either is one call, not a missing
feature.
Hardening built in
The validation pipeline enforces what the specifications say a careless implementation forgets: a key that declares an alg is never used for another algorithm when producing or verifying a JWS (RFC 8725 Section 3.1; JWE key unwrapping selects by kid and the header's alg, so a decryption key's declared alg is not a filter there). An HMAC key shorter than its hash output is rejected (RFC 7518 Section 3.2).
A crit header names only parameters a registered handler understands, and an unhandled critical parameter rejects the token, on the JWE envelope as on the JWS (RFC 7515 Section 4.1.11). A host that does understand such a parameter registers a handler for it by name, AddCriticalHeaderHandler<MyHandler>("my-ext"): the name is the registration key, so it cannot be claimed without a handler behind it.
Replay protection
Every JWT profile that forbids replay asks the same question - has this identifier been presented before? - so the primitive lives here rather than in each of them: IReplayCache reserves an identifier and answers whether the sighting is the first, in one call, so no caller can read, decide and write in three steps another caller slips between.
services.AddSingleton<IReplayCache>(provider =>
provider.CreateService<DistributedReplayCache>(Dependency.Override("MyApp:ReplayPrevention:")));
The shipped implementation stores in the host's IDistributedCache, so a single-instance deployment gets process-local behaviour and a scaled-out one gets shared memory by swapping the store. That store offers Get and Set and no compare-and-set, which makes the answer probabilistic within one cache round trip - enough for the profiles that accept it (RFC 9449 Section 11.1 for DPoP proofs, RFC 8935 Section 2 for redelivered Security Event Tokens), and replaceable behind the same interface by a backend-native primitive where it is not.
External keys
Signing and decryption do not require the private key to live in the process: the custodian seam delegates the cryptographic operation to an external holder - AddVaultCustodian for HashiCorp Vault / OpenBao (Abblix.JWT.Vault), AddAzureCustodian for Azure Key Vault (Abblix.JWT.Azure), AddCustodian<T> for one of your own.
Each opens a placement choice you then name, and that is the security posture: UseKeysInCustodian keeps every private half outside the process, UseKeysInProcess mints keys here and has the custodian only seal them.
The placement calls live in this package, which is what lets a host consume JWTs without being an OpenID Provider and still wire a custodian - no OIDC server anywhere in its graph. A backend package adds the transport for its own vault; with your own custodian this reference is the only one you need:
services
.AddJsonWebTokens()
.AddCustodian<MyCustodian>()
.UseKeysInCustodian(new CustodianHeldKeys { SigningKeyName = "sign" });
EXTERNAL_KEYS.md is the shared model, including what a host without our key provider does to publish the custodian's keys itself.
Key rings
Keys the host does not supply itself are minted and rotated by a key ring. AddInMemoryKeyRing(policy)
keeps them in the process: right for a single instance, and wrong for several, since each replica mints
its own and nothing fails at startup - sign-ins simply break for whoever lands on the wrong replica. So
a host that has registered an IKeyRingStore, which is how keys are shared, is refused here.
AddKeyRing(policy) is the shared form: it seals each minted key to the custodian's key-encryption
key and publishes it through that store, which the builder it returns supplies.
What the ring's keys are then used for is the caller's business - an OpenID Provider publishes them at its JWKS endpoint, another host protects stored sessions with them - which is why the ring lives beside the key material rather than beside either consumer.
Implemented standards
- JSON Web Signature (JWS): RFC 7515
- JSON Web Encryption (JWE): RFC 7516
- JSON Web Key (JWK): RFC 7517
- JWK Thumbprint: RFC 7638
- JSON Web Algorithms (JWA): RFC 7518
- JSON Web Token (JWT): RFC 7519
- AES Key Wrap: RFC 3394
Part of the Abblix product family
Abblix.JWT is the token layer under Abblix.OIDC.Server and Abblix.SecurityEvents; the full family lives in the repository.
License
Abblix.JWT is licensed under the Apache License 2.0.
Contacts
- General inquiries: info@abblix.com
- Support and security reports: support@abblix.com
- Website: Abblix OIDC Server
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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
- Abblix.DependencyInjection (>= 2.4.0)
- Abblix.Utils (>= 2.4.0)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Options (>= 10.0.8)
-
net8.0
- Abblix.DependencyInjection (>= 2.4.0)
- Abblix.Utils (>= 2.4.0)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Options (>= 10.0.8)
- System.Linq.Async (>= 7.0.1)
-
net9.0
- Abblix.DependencyInjection (>= 2.4.0)
- Abblix.Utils (>= 2.4.0)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Options (>= 10.0.8)
- System.Linq.Async (>= 7.0.1)
NuGet packages (6)
Showing the top 5 NuGet packages that depend on Abblix.JWT:
| Package | Downloads |
|---|---|
|
Abblix.OIDC.Server
OpenID Connect and OAuth 2.0 server for ASP.NET Core, certified by the OpenID Foundation. Add a complete identity provider and authorization server to your own .NET application: every OIDC flow, PKCE, PAR, DPoP, JARM, CIBA, device flow, token exchange and FAPI 2.0. Runs on .NET 8, 9 and 10. |
|
|
Abblix.OIDC.Server.MVC
ASP.NET Core MVC integration for Abblix OIDC Server, the certified OpenID Connect and OAuth 2.0 provider. Controllers, model binding and routing for every protocol endpoint: add it to a controller-based application and get a complete identity provider. |
|
|
Abblix.SecurityEvents
Security Event Tokens (RFC 8417) for .NET with Subject Identifiers (RFC 9493), push (RFC 8935) and poll (RFC 8936) delivery, and OpenID Back-Channel Logout. Build, sign, deliver and validate security events between identity providers and relying parties. |
|
|
Abblix.JWT.Azure
Azure Key Vault integration for Abblix JWT and Abblix OIDC Server: sign tokens and unwrap keys with private keys that never leave the vault. Keep your OpenID Connect signing keys in Azure. |
|
|
Abblix.OIDC.Server.MinimalAPI
ASP.NET Core Minimal API integration for Abblix OIDC Server, the certified OpenID Connect and OAuth 2.0 provider. Every protocol endpoint as a route handler with no MVC dependency: the lightest way to host an identity provider in .NET. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.4.0 | 317 | 9/5/2026 |
| 2.3.0 | 239 | 6/9/2026 |
| 2.2.0 | 401 | 2/18/2026 |
| 2.1.0 | 603 | 12/8/2025 |
| 2.0.1 | 698 | 12/1/2025 |
| 2.0.0 | 272 | 11/26/2025 |
| 1.6.0 | 365 | 8/14/2025 |
| 1.5.0 | 328 | 6/25/2025 |
| 1.4.0 | 360 | 4/9/2025 |
| 1.3.1 | 326 | 12/3/2024 |
| 1.3.0.1 | 298 | 11/28/2024 |
| 1.3.0 | 296 | 11/13/2024 |
| 1.2.0.1 | 284 | 10/16/2024 |
| 1.2.0 | 279 | 10/11/2024 |
| 1.1.0 | 398 | 7/9/2024 |
| 1.0.100 | 327 | 5/3/2024 |
A JWT and JOSE toolkit that uses the .NET cryptographic primitives and System.Text.Json directly, so a token round-trips with no second serializer and no key-model translation. Sign and verify with every RSA, ECDSA and HMAC algorithm of RFC 7518; encrypt with every key-management algorithm of RFC 7516, including AES key wrapping and opt-in password-based encryption with a bounded work factor; publish and consume JSON Web Keys and key sets with thumbprints and typed header accessors; process critical headers; prevent replay of single-use tokens. Keys can live outside the process: a custodian seam lets HashiCorp Vault, OpenBao Transit or Azure Key Vault hold the private half, served by the companion packages. Clock tolerance travels as one value with separate past and future halves, so a FAPI 2.0 window is expressed exactly. The token layer behind Abblix OIDC Server, usable on its own. Full details: https://github.com/Abblix/Oidc.Server/releases/tag/v2.4