Dependency-free JOSE / JWT for .NET — built only on the BCL. JWE, JWS and Nested JWT, with a strict, anti-oracle API.
{ "email": "ana@acme.cl",
"role": "admin", "id": 42 }
Anyone who intercepts it can read this.
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NN•••••••••••••••••••••••
Opaque without the key. Claims hidden.
A normal JWT is signed, but not encrypted — paste it into jwt.io and you'll read every claim. The signature stops tampering, not reading. JWE closes that gap.
Integrity & authenticity. Guarantees nobody altered the token. But the claims stay readable.
Confidentiality. The payload is encrypted (dir + AES-256-GCM) and opaque without the key.
Sign and encrypt: a JWS wrapped inside a JWE. Integrity and confidentiality together.
Sign, encrypt and validate — the API stays the same; encryption is a single extra call.
using Matios.Security.Jose;
using Matios.Security.Jwt;
var key = SymmetricJoseKey.FromBase64Url(signingKeyB64, "sig-2026");
string token = new JwtBuilder()
.Issuer("my-platform")
.Subject(userId)
.Lifetime(TimeSpan.FromMinutes(30))
.Claim("role", "admin")
.SignWith(key)
.Create();// Same builder — add ONE line to make it encrypted (Nested JWT).
string token = new JwtBuilder()
.Subject(userId)
.Lifetime(TimeSpan.FromMinutes(30))
.Claim("role", "admin")
.SignWith(signingKey)
.EncryptWith(encryptionKey) // ← JWE dir + A256GCM
.Create();
// The token is now opaque — claims unreadable without the key.JwtClaims claims = JwtValidator.Validate(token, new JwtValidationParameters
{
SigningKey = signingKey,
DecryptionKey = encryptionKey, // set ⇒ token MUST be JWE (no downgrade)
ValidIssuer = "my-platform"
});
string role = claims.GetClaim<string>("role");// Anti-oracle: one generic exception; the reason is in FailureCode (log only).
if (!JwtValidator.TryValidate(token, parameters, out var claims, out var failure))
{
logger.LogWarning("Token rejected: {Code}", failure); // detail → server log
return Unauthorized(); // generic → client
}The token header never decides on its own. You declare what's accepted; everything else is cleanly rejected.
alg/enc. No alg:"none", ever. Anti algorithm-confusion.ClockSkew + RequireExpiration on JwtValidationParameters. Sensible defaults..Claim(name, value) takes any JSON-serializable value — lists, POCOs, ids. Typed reads with GetClaim<T>.DecryptionKey and a plain JWS is rejected — "encrypted only" becomes enforceable.zip rejected · crit rejectedCompression-oracle class and unknown critical headers are refused by design.Built only on the BCL (System.Security.Cryptography + System.Text.Json). Tiny, readable, auditable surface.
Microsoft.IdentityModel can't even issue JWE with A256GCM — and can't decrypt it on Linux. This library does both, on every platform.
Conformance checked against an independent implementation (Microsoft.IdentityModel) plus the official RFC 7515 test vector.
Released on a strict, conformance-first cadence. Every version is interop-verified before it ships.
dir + A256GCM) · JWS (HS256) · Nested JWT · strict, anti-oracle. Available on NuGet today.Open source · MIT · .NET 10
dotnet add package Matios.Security