JWT Decoder
Decodes a JWT header and payload and explains every registered claim. Decode only — the signature is never checked.
A leading Bearer, a whole Authorization: line, quotes and line breaks are stripped on paste.
Decoded, not verified. The signature is not checked, so this says what the token claims — not whether the claim is true. A forged token decodes just as cleanly.
{
"alg": "HS256",
"typ": "JWT",
"kid": "2026-07"
}{
"iss": "https://auth.drishlabs.com",
"sub": "user_8f21c4",
"aud": [
"drishlabs-web",
"drishlabs-api"
],
"iat": 1785229200,
"nbf": 1785229200,
"exp": 2051222400,
"jti": "b7c0f1e2-9a44-4c31-8f0a-6d2e5b7c1a90",
"scope": "read:products write:contact",
"email_verified": true
}issIssuerhttps://auth.drishlabs.com
Who minted the token. A verifier matches it against the issuer it expects.
subSubjectuser_8f21c4
Who or what the token is about — normally a stable id, not an email address.
audAudiencedrishlabs-web, drishlabs-api
Who the token is for. A recipient not named here must reject it.
expExpiration Time2035-01-01 00:00:00 UTC
2051222400
After this instant the token must be rejected.
nbfNot Before2026-07-28 09:00:00 UTC
1785229200
Before this instant the token must be rejected.
iatIssued At2026-07-28 09:00:00 UTC
1785229200
When the token was minted. Used to judge its age.
jtiJWT IDb7c0f1e2-9a44-4c31-8f0a-6d2e5b7c1a90
A unique id for this token, for replay detection and revocation lists.
Other claims: scope, email_verified. Public or private claims — their meaning is the issuer’s, not the spec’s.
token = base64url(header) . base64url(payload) . signature — exp, nbf and iat are seconds since 1970-01-01T00:00:00Z
Decode only. The signature is never checked here, so a token that decodes cleanly can still be forged, expired, or meant for a different audience. Verify against the issuer key on your server, and treat any production token pasted into a decoder as exposed.
How it works
Paste the token. A leading Bearer, a whole Authorization: line, surrounding quotes and the line breaks from a wrapped paste are all stripped first — a JWT in compact form contains no whitespace, so anything left over is packaging.
The dot-separated segments are split apart. Three means a signed token (JWS); five means an encrypted one (JWE), where only the first segment is readable.
Segments one and two are decoded as Base64URL — the RFC 4648 §5 alphabet, which uses - and _ instead of + and / and drops the = padding, so the padding is restored from the length before decoding. Each is parsed as JSON and printed with two-space indent, independently, so a broken payload still shows you the header.
The seven registered claims of RFC 7519 §4.1 are named and explained. exp, nbf and iat are NumericDate — seconds since 1970-01-01T00:00:00Z, not milliseconds — so each is rendered as a UTC timestamp with how long until or since it.
The third segment is measured and left alone. Nothing here checks it, and the instrument says so on screen rather than only here.
The three segments, and what NumericDate is
A JWT in JWS Compact Serialization is three Base64URL segments joined by dots. The first two are JSON — a JOSE header and a claims set — and the third is the signature over the first two, joined by their dot, as ASCII. That is why the header and payload are readable to anyone holding the token: Base64URL is an encoding, not a cipher. The three time claims are NumericDate, defined in RFC 7519 §2 as the number of seconds since the epoch, ignoring leap seconds. A value in milliseconds decodes to a date tens of thousands of years out, which is the single most common bug in a hand-rolled token.
token = base64url(header) . base64url(payload) . signature | exp, nbf, iat = seconds since 1970-01-01T00:00:00Z
Use cases
Working out why an API returned 401
Three claims cause most rejections and all three are on screen at once: exp has passed, aud names an audience the API is not, or iss is the staging issuer against a production verifier. Check those before assuming the key is wrong — a signature failure and an expiry failure both surface as 401 in most stacks.
Checking when a token expires
exp is seconds since the epoch. If the UTC date lands beyond the year 9999 the issuer wrote milliseconds, and every verifier will treat the token as valid for the next fifty thousand years. The reverse mistake — dividing an already-correct value — puts exp in 1970 and the token is dead on arrival.
Debugging an OIDC ID token
An ID token is a JWT and decodes here: sub, aud (the client id), iss, nonce, auth_time and email_verified. An access token from the same provider often is not — Google and Okta hand out opaque strings for some flows, and pasting one produces a segment-count error rather than a payload, which is the tell.
nbf, iat and clock skew
A token minted on a server whose clock is 40 seconds ahead is not yet valid on a machine whose clock is right. That is why verifiers allow leeway — typically 30 to 60 seconds — on nbf and exp. Comparing iat here against the wall clock is how you find the skew rather than guessing at it.
alg none and the algorithm confusion attack
RFC 7518 §3.6 defines "none" as the Unsecured JWS. A library that honours the header instead of pinning its own expected algorithm will accept a token anyone can forge, and the related RS256-to-HS256 confusion attack verifies a forged token against the public key as if it were an HMAC secret. RFC 8725 §3.1 is explicit: the verifier decides the algorithm, never the token.
kid and key rotation
The header kid names which key in the issuer JWKS signed this token. When rotation breaks something, comparing the kid in the token against the keys currently published at the /.well-known/jwks.json endpoint tells you whether the token predates the rotation or the endpoint is stale.
Auditing what a payload is carrying
Anyone holding the token can read it, so an email address, an internal user table id or a role list in the payload is disclosed to every intermediary, every log line and every browser extension. Decode a production token and look at what is actually in it — that is the audit, and it takes one paste.
JWE versus JWS
Five segments means JWE: protected header, encrypted key, initialisation vector, ciphertext, authentication tag. Only the header is readable, and this tool shows that and stops. Decrypting requires the recipient key, which is the whole point of the format.
Reading a token out of a log or a curl transcript
Tokens usually arrive wrapped in something — an Authorization: Bearer prefix, JSON quotes, a trailing comma, or line breaks inserted by a terminal. All of that is stripped on paste, so a copied header line decodes without hand-cleaning it first.
Questions
- Does this verify the signature?
- No, and it is not going to. Verification needs the issuer key — an HMAC secret or a public key from a JWKS endpoint — and a decision about which algorithms are acceptable. That belongs on your server, not in a web page you paste a production token into. A token that decodes cleanly can still be forged, expired, replayed, or meant for a different audience.
- Is the token sent anywhere?
- No. The split, the Base64URL decode and the JSON parse all run in the page. Nothing is uploaded, logged, or kept between visits. That said, a token in a browser tab is still a live credential — if you paste a production token into any decoder, treat it as exposed and rotate it.
- Why does exp show a date in the year 58000?
- The issuer wrote milliseconds. RFC 7519 §2 defines exp, nbf and iat as NumericDate: seconds since 1970-01-01T00:00:00Z. Multiplying by 1000 before signing pushes the expiry roughly fifty thousand years out, so the token never expires and no verifier complains.
- What does alg: none mean?
- It is the Unsecured JWS from RFC 7518 §3.6 — a token with an empty signature segment. It is legitimate only where integrity is guaranteed by something else, and it is an attack everywhere else: a verifier that reads the algorithm out of the header will accept a token any stranger can write. The check here is case-insensitive because "None" and "NONE" are the usual way past a naive comparison.
- Can it decode an encrypted token?
- No. A five-segment token is a JWE and its payload is ciphertext. The protected header is shown, because that part is plaintext and names the algorithms, but reading the claims requires the recipient private key.
- Are the registered claims required?
- None of the seven is mandatory — RFC 7519 §4.1 marks every one OPTIONAL. What makes a claim required is your validation policy: an API that ignores aud will happily accept a token minted for a different service, and a token with no exp never expires on its own.
- Why is the payload readable at all?
- A signed JWT is signed, not encrypted. Base64URL is a reversible mapping with no key, so every intermediary and every log that touches the token can read the claims. Put an identifier in the payload, not a secret.
More tools
One of 18 free tools here. They come out of building utility apps for macOS, iOS and the browser — all of it by one person.
See the apps