Issues JWE that the official .NET stack can't

Encrypt your JWTs,
not just sign them.

Dependency-free JOSE / JWT for .NET — built only on the BCL. JWE, JWS and Nested JWT, with a strict, anti-oracle API.

dotnet add package Matios.Security
Signed JWT (JWS)readable
{ "email": "ana@acme.cl",
  "role": "admin", "id": 42 }
Anyone who intercepts it can read this.
↓ wrap in JWE ↓
Encrypted (JWE)🔒 locked
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NN••••••••••••••••••••••• Opaque without the key. Claims hidden.
The gap

Almost every dev knows JWT.
Very few know JWE.

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.

JWS — signing

Integrity & authenticity. Guarantees nobody altered the token. But the claims stay readable.

🔒

JWE — encryption

Confidentiality. The payload is encrypted (dir + AES-256-GCM) and opaque without the key.

Nested JWT — both

Sign and encrypt: a JWS wrapped inside a JWE. Integrity and confidentiality together.

Quick start

One line away from encrypted tokens

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
}
Usage & configuration

Strict by design — the caller decides

The token header never decides on its own. You declare what's accepted; everything else is cleanly rejected.

Caller-side whitelistsYou declare the accepted alg/enc. No alg:"none", ever. Anti algorithm-confusion.
Clock skew & lifetimeClockSkew + RequireExpiration on JwtValidationParameters. Sensible defaults.
Dynamic claims.Claim(name, value) takes any JSON-serializable value — lists, POCOs, ids. Typed reads with GetClaim<T>.
No silent downgradeSet a 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.
Keys wiped on disposeKey material is never exposed and is zeroed from memory. Constant-time HMAC compare.
Why Matios.Security

Small, auditable, and it does what Microsoft's stack can't

0

Zero dependencies

Built only on the BCL (System.Security.Cryptography + System.Text.Json). Tiny, readable, auditable surface.

Encrypts where MS can't

Microsoft.IdentityModel can't even issue JWE with A256GCM — and can't decrypt it on Linux. This library does both, on every platform.

Interop-verified

Conformance checked against an independent implementation (Microsoft.IdentityModel) plus the official RFC 7515 test vector.

Versions & roadmap

Where it's at — and where it's going

Released on a strict, conformance-first cadence. Every version is interop-verified before it ships.

v0.1.1 · now
JOSE / JWT coreJWE (dir + A256GCM) · JWS (HS256) · Nested JWT · strict, anti-oracle. Available on NuGet today.
Asymmetric signingRS256 — issuer and validator no longer need to share a secret. Next on the roadmap.

Add it to your project

Open source · MIT · .NET 10

dotnet add package Matios.Security