How to inspect a JWT without compromising it
A JWT is three Base64 strings joined by dots. Here is what each part means, what the decoder shows, and why the signature stays secret.
You hit an auth endpoint, get back a token that starts with eyJ, and you need to know what's inside it. The JWT Decoder shows you — locally, without sending the token anywhere.
The three parts
A JWT looks like aaa.bbb.ccc. Three Base64URL-encoded strings separated by dots.
1. Header. Tells the verifier what algorithm was used to sign the token.
{ "alg": "HS256", "typ": "JWT" }
2. Payload. The actual claims. The interesting part.
{
"sub": "user-123",
"email": "ada@example.com",
"exp": 1735689600,
"iat": 1735603200,
"roles": ["admin"]
}
3. Signature. Cryptographic proof that the token was issued by someone holding the secret (HS256) or private key (RS256/ES256). The verifier checks this; the decoder cannot.
The standard claims
JWTs have a set of registered claim names. The ones you'll see most:
iss(issuer): who created the token. "https://my-auth-server" or similar.sub(subject): the user or principal the token is about.aud(audience): who the token is intended for. The verifier should check that this matches their service.exp(expiration): Unix timestamp when the token stops being valid.iat(issued at): Unix timestamp when the token was created.nbf(not before): Unix timestamp when the token starts being valid.jti(JWT ID): a unique identifier for this token, often used to enable revocation.
The decoder shows exp and iat as human-readable dates so you can answer "is this expired?" in a glance.
What's safe to paste
The header and payload of a JWT are public by design. Anyone who has the token can already read them. Pasting a JWT into the decoder reveals nothing the token holder doesn't already know.
The signature is a cryptographic check, not a secret per se. Sharing the full token in a chat or paste site is still risky, though: anyone who has the full token can use it as the user until it expires. Treat JWTs like passwords on transit, but the act of decoding them locally is fine.
For maximum caution: paste expired tokens or test tokens whenever possible. The decoder doesn't care.
Three things the decoder doesn't do
- Verify the signature. That requires the secret or public key, which only the issuer and verifier should have.
- Decode JWEs. A JWE (encrypted JWT) has five parts and the payload is encrypted, not just signed. Most "JWTs" you see in the wild are JWS (signed but not encrypted) — that's what this decoder handles.
- Generate new tokens. It's a read-only inspector. To mint test JWTs you need a server-side library (or
jwt.io's playground).
Common gotchas
expis in seconds, not milliseconds. A common bug: developers compareDate.now()(milliseconds) againstexp(seconds) and conclude every token is expired.- Clock skew matters. If the issuer's clock is 30 seconds ahead of the verifier's, tokens with
nbfin the immediate future are rejected. Allow a few seconds of leeway. - Tokens are not opaque. Don't put sensitive data in the payload (full address, government IDs, etc.). Anyone holding the token reads everything.
For debugging "why is my token being rejected" — paste, read, find the broken claim, ship the fix.