JwtDecoder.Core
1.2.0
See the version list below for details.
dotnet add package JwtDecoder.Core --version 1.2.0
NuGet\Install-Package JwtDecoder.Core -Version 1.2.0
<PackageReference Include="JwtDecoder.Core" Version="1.2.0" />
<PackageVersion Include="JwtDecoder.Core" Version="1.2.0" />
<PackageReference Include="JwtDecoder.Core" />
paket add JwtDecoder.Core --version 1.2.0
#r "nuget: JwtDecoder.Core, 1.2.0"
#:package JwtDecoder.Core@1.2.0
#addin nuget:?package=JwtDecoder.Core&version=1.2.0
#tool nuget:?package=JwtDecoder.Core&version=1.2.0
JwtDecoder.Core
Offline, hardened JSON Web Token decoding and signature verification for .NET 8+.
This is the shared core library that powers:
jwtdecode— single-file Native AOT CLI for Windows, Linux, and macOS.JwtDecoder— PowerShell 7.4+ binary module.
Why another JWT library?
Most JWT libraries try to cover the whole identity workflow — issuance, refresh, JWKS discovery, OIDC flows, key rotation. That's exactly the kind of code that grows attack surface faster than tests can cover it. JwtDecoder.Core does one thing only: it takes a compact JWS token and (optionally) a verification key, and tells you whether the signature checks out.
Hardening properties
- Offline by design — never opens a socket. Filesystem reads are the only side-channel.
- AOT-safe — no reflection-based serialization, no dynamic code, zero NuGet dependencies. Works in
PublishAotapps. - Algorithm-confusion guard — refuses to verify an
HS*token with a PEM-looking key file. - Private-key refusal — PEM blocks labelled
PRIVATE KEYare rejected; verification only needs the public key. - JOSE curve binding —
ES256↔P-256,ES384↔P-384,ES512↔P-521. Mismatched curves are refused. - Constant-time HMAC comparison via
CryptographicOperations.FixedTimeEquals. - DoS-bounded I/O — token ≤ 1 MiB, key file ≤ 64 KiB, decoded segment ≤ 256 KiB, JSON
MaxDepth = 64. - Memory hygiene — secret bytes, decoded buffers, signing input, intermediate base64 / char arrays are all zeroed in
Dispose/finally. - Strict parsing — duplicate header/payload property names are rejected (parser-differential defense);
alg: nonealways returnsVerified=false.
Install
dotnet add package JwtDecoder.Core
Usage
The JwtTools static class is the easiest entry point:
using JwtDecoder.Core;
// Decode only (no signature check)
using var jwt = JwtTools.Decode(token);
Console.WriteLine(jwt.Algorithm); // e.g. "HS256"
Console.WriteLine(jwt.Payload.RootElement.GetProperty("sub").GetString());
// One-shot HMAC verification
var outcome = JwtTools.VerifyHmac(token, "your-256-bit-secret");
Console.WriteLine(outcome.Verified ? "OK" : $"Failed: {outcome.Error}");
// Decode + verify HMAC in one call (avoids double-parsing the token)
var (decoded, hmacResult) = JwtTools.DecodeAndVerifyHmac(token, secretBytes);
using (decoded)
{
if (hmacResult.Verified)
{
var role = decoded.Payload.RootElement.GetProperty("role").GetString();
// ...
}
}
// RSA / RSA-PSS (RS256/384/512, PS256/384/512)
using var rsa = RSA.Create();
rsa.ImportFromPem(pemString);
var rsaResult = JwtTools.VerifyRsa(token, rsa);
// ECDsa (ES256/384/512)
using var ec = ECDsa.Create();
ec.ImportFromPem(pemString);
var ecResult = JwtTools.VerifyEcdsa(token, ec);
// File-based — same rules as the CLI (alg inferred + cross-checked against file)
var fileResult = JwtTools.VerifyWithKeyFile(token, "./hs256-secret.txt");
// Query a single claim by path (returns a cloned JsonElement, safe to use after the call)
JsonElement? sub = JwtTools.Query(token, "payload.sub");
Console.WriteLine(sub?.GetString()); // "1234567890"
// Or use the Try-pattern
if (JwtTools.TryQuery(token, "payload.roles[0]", out var role))
Console.WriteLine(role.GetString());
// When you already have a decoded Jwt, the instance methods avoid re-parsing
using var jwt2 = JwtTools.Decode(token);
if (jwt2.TryQuery("header.alg", out var algEl))
{
Console.WriteLine(algEl.GetString());
// Format helpers — JSON-canonical (safe for terminals) or raw (string scalars unwrapped)
string asJson = JwtQuery.FormatJson(algEl); // "HS256"
string asRaw = JwtQuery.FormatRaw(algEl); // HS256
}
Query path grammar
Paths use dot notation with [N] for array indices. The first segment may be header or payload to switch scope; otherwise the scope defaults to payload (shorthand). Quoted segments (payload."x5t#S256") escape names that contain non-identifier characters.
| Path | Meaning |
|---|---|
payload.sub |
The sub claim in the payload. |
header.alg |
The alg parameter in the JOSE header. |
sub |
Shorthand for payload.sub. |
payload.roles[0] |
First element of the roles array. |
payload.address.city |
Nested property walk. |
payload."x5t#S256" |
Quoted segment for keys with non-identifier characters. |
payload |
The whole payload object. |
JwtQueryPath.Parse parses a single path; JwtQueryPath.ParseMany parses a comma-separated list (commas inside quoted segments or [...] are not treated as separators). A path that does not resolve returns false from TryQuery / null from Query; a JSON null value at the target is a successful match.
If you need more control (e.g. reusing an RSA across many verifications, owning the KeyMaterial lifetime, or accessing the raw signing input), drop down to the underlying Jwt, JwtVerifier, KeyMaterial, and KeyLoader types directly — JwtTools is just a thin convenience layer over them.
Supported algorithms
| Family | Algorithms | Key type accepted |
|---|---|---|
| HMAC | HS256, HS384, HS512 | Raw secret bytes |
| RSA-PKCS#1 | RS256, RS384, RS512 | RSA public key |
| RSA-PSS | PS256, PS384, PS512 | RSA public key |
| ECDSA | ES256, ES384, ES512 | EC public key (curve-bound) |
alg: none is parseable for inspection but always returns Verified=false with a clearly worded security warning in VerifyOutcome.Error.
See also
If you need to fetch the verification key from a JWKS endpoint (direct URL or via OIDC discovery), use the network‑capable companion package JwtDecoder.JwksFetcher — released in lock‑step with this package and exact‑pinned against it. JwtDecoder.Core itself remains offline by construction (zero NuGet dependencies, no System.Net.* references). Use the companion only when your application explicitly needs to reach out for keys.
License
MIT — see LICENSE.
| 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 was computed. 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 was computed. 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. |
-
net8.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on JwtDecoder.Core:
| Package | Downloads |
|---|---|
|
JwtDecoder.JwksFetcher
JWKS acquisition and OIDC discovery companion library for JwtDecoder.Core. Network-capable: fetches a JWKS (or discovers one via OIDC), validates it, selects the JWK matching a JWT's kid/alg/crv, and emits the public key as PEM. Defense in depth: HTTPS only, SSRF guards (including DNS rebinding via ConnectCallback and IPv4-mapped IPv6 unmapping), TLS 1.2/1.3 pinned, bounded sizes/timeouts/redirects, no on-disk cache, bearer stripped on every redirect. |
GitHub repositories
This package is not used by any popular GitHub repositories.