.NET Code Tips

Free .NET developer tool

JWT Decoder & Claims Inspector

Paste a JSON Web Token to read its header and claims, see when it expires, and look up what each Entra ID and ASP.NET Core claim means. The token never leaves your browser.

Runs entirely in your browser — nothing you paste is uploaded.

alg: HS256

Header

header.json
{
  "alg": "HS256",
  "typ": "JWT"
}
alg
HS256Signing algorithm. RS256 (RSA) and ES256 are asymmetric; HS256 uses a shared secret. "none" must never be accepted.
typ
JWTToken type, usually JWT (or at+jwt for OAuth access tokens).

Payload

payload.json
{
  "iss": "https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0",
  "aud": "api://contoso-api",
  "sub": "AAAAAAAAAAAAAAAAAAAAAIkzDFob42QjPykQfA2I4",
  "name": "Ada Lovelace",
  "preferred_username": "ada@contoso.com",
  "oid": "4f6a8b2c-1d3e-4f5a-9b0c-2d3e4f5a6b7c",
  "tid": "00000000-0000-0000-0000-000000000000",
  "scp": "Orders.Read Orders.Write",
  "roles": [
    "Admin"
  ],
  "iat": 1759914000,
  "nbf": 1759914000,
  "exp": 1759917600
}

Claims explained

ClaimValueMeaning
iss https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0 Issuer — who created and signed the token.
aud api://contoso-api Audience — who the token is intended for; your API must check it matches.
sub AAAAAAAAAAAAAAAAAAAAAIkzDFob42QjPykQfA2I4 Subject — the user or principal the token is about.
name Ada Lovelace Full name of the user.
preferred_username ada@contoso.com The username the user signs in with (often their UPN or email).
oid 4f6a8b2c-1d3e-4f5a-9b0c-2d3e4f5a6b7c Object ID — the user’s immutable ID in Entra ID; use this, not email, as a key.
tid 00000000-0000-0000-0000-000000000000 Tenant ID — the Entra ID directory the user signed in to.
scp Orders.Read Orders.Write Delegated scopes (permissions) granted to the app, space-separated.
roles ["Admin"] App roles assigned to the user or application.
iat 1759914000
2025-10-08 09:00:00 UTC
Issued at — when the token was created.
nbf 1759914000
2025-10-08 09:00:00 UTC
Not before — the token must be rejected before this.
exp 1759917600
2025-10-08 10:00:00 UTC
Expiration time — the token must be rejected after this.

Verify an HMAC signature (optional)

For HS256/384/512 tokens signed with a shared secret. Verification uses your browser's Web Crypto API; the secret is not stored or sent. RS256 and ES256 tokens are verified with the issuer's public keys instead.

Validating JWTs in ASP.NET Core

Decoding shows what a token claims; your API must still validate it — signature, issuer, audience and lifetime — before trusting any of it. The JwtBearer handler does all four from the issuer's OpenID Connect metadata:

Program.cs
// Program.cs — validate bearer tokens from Microsoft Entra ID (or any OIDC issuer)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://login.microsoftonline.com/{tenant-id}/v2.0";
        options.Audience = "api://contoso-api";
        options.MapInboundClaims = false;                 // keep "roles", "scp", "oid" as-is
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.FromMinutes(2),          // default is 5 minutes
            RoleClaimType = "roles",
            NameClaimType = "preferred_username",
        };
    });

// Reading claims in an endpoint
app.MapGet("/me", (ClaimsPrincipal user) => new
{
    Name = user.Identity?.Name,
    ObjectId = user.FindFirstValue("oid"),
    Scopes = user.FindFirstValue("scp")?.Split(' '),
}).RequireAuthorization();

Reading a token in code

To inspect a token without validating it — for logging or routing — use JsonWebTokenHandler from Microsoft.IdentityModel.JsonWebTokens, the faster successor to JwtSecurityTokenHandler:

ReadToken.cs
using Microsoft.IdentityModel.JsonWebTokens;

// Read (not validate!) a token's claims — e.g. for logging which tenant called you.
var jwt = new JsonWebTokenHandler().ReadJsonWebToken(token);
string? tenant = jwt.GetPayloadValue<string>("tid");
DateTime expiresUtc = jwt.ValidTo;

Login stuck in a redirect loop?

The classic Azure symptom — sign-in succeeds, then bounces back to the identity provider forever — is usually a cookie that never sticks: the app thinks it is on HTTP behind a TLS-terminating proxy, so SameSite=None cookies are dropped. In ASP.NET Core add app.UseForwardedHeaders() (with XForwardedProto) before authentication; in OWIN apps the equivalent fix was the SystemWebCookieManager.

Frequently asked questions

Is it safe to paste a JWT into an online decoder?

Only if the decoder runs locally. A JWT is a bearer credential: anyone holding an unexpired token can call the API as you. This decoder runs entirely in your browser with no network calls, so the token never leaves the page. Even so, prefer expired or test tokens when you can.

Does decoding a JWT verify it?

No. The header and payload are only Base64Url-encoded JSON, so anyone can read them. Verification means checking the signature with the issuer’s key plus the issuer, audience and lifetime claims, which ASP.NET Core’s JwtBearer handler does through TokenValidationParameters. This tool can verify HS256 signatures if you supply the shared secret; RS256 tokens are verified against the issuer’s public keys from its JWKS endpoint.

Why is my token rejected as expired when it hasn’t expired yet?

Check the server’s clock and the nbf (not before) claim: a token used a moment after issue on a server whose clock runs slow can fail. ASP.NET Core allows five minutes of ClockSkew by default in each direction, so a token that is rejected at the edge is usually a clock or time zone problem, or a token from a different environment. Compare exp and nbf above with the server’s UTC time.

Why do my claims have long URI names in ASP.NET Core?

The JwtBearer handler historically mapped short JWT claim names to long WS-Federation URIs, so "role" became "http://schemas.microsoft.com/ws/2008/06/identity/claims/role". Set options.MapInboundClaims = false in AddJwtBearer to keep the original short names, and set TokenValidationParameters.RoleClaimType = "roles" (or "role") so [Authorize(Roles = ...)] still finds them. With mapping on, User.FindFirst(ClaimTypes.Role) works because ClaimTypes.Role is the long URI.