How to Decode a JWT in .NET and C# (With and Without Library)
- Manual base64 approach — decode any JWT in C# with no NuGet package
- Using System.IdentityModel.Tokens.Jwt (JwtSecurityTokenHandler)
- Reading specific claims like sub, email, and custom claims in C#
- The difference between decoding and validating in .NET
Table of Contents
In .NET you can decode a JWT two ways: manually using Convert.FromBase64String (no package needed) or with the JwtSecurityTokenHandler from the Microsoft.IdentityModel library. Both give you the claims. Here is how each works.
Decode JWT Payload in C# Without a Library
using System;
using System.Text;
using System.Text.Json;
public static JsonDocument DecodeJwtPayload(string token)
{
string[] parts = token.Split('.');
if (parts.Length != 3)
throw new ArgumentException("Invalid JWT format");
string payload = parts[1];
// Convert base64url to standard base64
payload = payload.Replace('-', '+').Replace('_', '/');
// Add padding
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] bytes = Convert.FromBase64String(payload);
string json = Encoding.UTF8.GetString(bytes);
return JsonDocument.Parse(json);
}
// Usage
using var doc = DecodeJwtPayload(token);
var root = doc.RootElement;
string sub = root.GetProperty("sub").GetString();
long exp = root.GetProperty("exp").GetInt64();
var expDate = DateTimeOffset.FromUnixTimeSeconds(exp);
Decode JWT with System.IdentityModel.Tokens.Jwt
Install the NuGet package: System.IdentityModel.Tokens.Jwt
using System.IdentityModel.Tokens.Jwt;
// Decode without validation (inspection only)
var handler = new JwtSecurityTokenHandler();
var jwt = handler.ReadJwtToken(token);
// Access header claims
string algorithm = jwt.Header.Alg;
// Access payload claims
string sub = jwt.Subject; // shortcut for "sub" claim
string issuer = jwt.Issuer;
DateTime expiry = jwt.ValidTo;
// Access any claim by type
string email = jwt.Claims
.FirstOrDefault(c => c.Type == "email")?.Value;
// All claims as a dictionary
var claimsDict = jwt.Claims
.ToDictionary(c => c.Type, c => c.Value);
handler.ReadJwtToken() decodes without verifying the signature. For verified decoding, use handler.ValidateToken() with a TokenValidationParameters object.
Reading Custom and Namespaced Claims in .NET
// Custom claim (short name)
string orgId = jwt.Claims
.FirstOrDefault(c => c.Type == "org_id")?.Value;
// Namespaced claim (Auth0/Okta style)
string role = jwt.Claims
.FirstOrDefault(c => c.Type == "https://myapp.com/role")?.Value;
// Array claim (e.g. roles or permissions)
var permissions = jwt.Claims
.Where(c => c.Type == "permissions")
.Select(c => c.Value)
.ToList();
// Note: JwtSecurityTokenHandler maps some standard claim names.
// "sub" becomes ClaimTypes.NameIdentifier in some contexts.
// Use the raw Type string to be safe.
Be aware that .NET's ClaimsPrincipal infrastructure maps some JWT claim names to longer URN-style names (for example sub may map to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier). When reading raw JWT claims, use the string type directly.
Decoding vs Validating JWTs in .NET
For production server code, use validation rather than just decoding:
var validationParams = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://YOUR-ISSUER.com",
ValidateAudience = true,
ValidAudience = "your-api-identifier",
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(secretKey))
};
try
{
var principal = handler.ValidateToken(token, validationParams, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
string sub = jwtToken.Subject;
}
catch (SecurityTokenExpiredException)
{
// Token expired
}
catch (SecurityTokenInvalidSignatureException)
{
// Tampered token
}
Verify Your JWT Structure First
Paste your token above to confirm its claim names before writing .NET parsing code — saves debugging time.
Open Free JWT DecoderFrequently Asked Questions
Which NuGet package should I use for JWT in .NET?
System.IdentityModel.Tokens.Jwt is the Microsoft-maintained standard. For RS256/OIDC with JWKS support, also add Microsoft.IdentityModel.Protocols.OpenIdConnect. The community package System.Text.Json can handle manual decode without any JWT-specific package.
How do I decode a JWT in ASP.NET Core middleware?
ASP.NET Core's AddAuthentication().AddJwtBearer() validates and decodes JWTs automatically, making claims available via User.Claims or HttpContext.User. For manual decode outside the pipeline, use JwtSecurityTokenHandler.ReadJwtToken() as shown above.
Why does jwt.Subject return null in .NET?
Subject maps to the "sub" claim. If the JWT does not have a "sub" claim, Subject will be null. Check the raw claims via jwt.Claims to see what claim names are actually present in the token.

