JWT Decoder: Read a Token's Header, Claims and Signature
A JWT decoder does one job: it takes the string your application passes around and turns it back into the three JSON objects it was built from. No key is needed for that. The token is base64url, not encrypted, so the header and payload are readable by anyone who holds the string. That fact alone explains most of the confusion around JSON Web Tokens, and it also explains why a decoder is useful in debugging and useless as a security control.
This page walks through what the decoder shows, what each registered claim means, how to tell the three parts apart, and where tokens go wrong in practice.
What the three parts of a JWT contain
A compact JWT is three base64url segments joined by dots. The first segment is the header, a small JSON object that names the algorithm and usually the token type. The second is the payload, a JSON object of claims. The third is the signature, computed over the first two segments with a key.
Base64url is not base64. The alphabet swaps + for - and / for _, and padding is dropped. This is why a token pasted from a log sometimes fails to decode in a generic base64 tool: the characters that base64 expects are simply not there. A decoder that understands base64url restores the padding before parsing.
The two JSON objects are not signed individually. The signature covers the exact ASCII bytes of header.payload, including the dot. Change one character anywhere in either segment and the signature no longer matches, even if the JSON still parses. This is the property that makes the third segment meaningful, and it is the property that a decoder cannot check without the key.
A fourth and fifth segment means the token is not a JWT. Five parts is JWE, which is encrypted, and the payload will not parse as JSON because it is ciphertext. Two parts, or a colon-separated string, points to a different family entirely: PASETO, Macaroon, Fernet and dozens of others have their own layouts. Recognising the family from the shape of the string is the first thing a decoder should do, because the wrong assumption about the format produces nonsense claims and a false sense of understanding.
What the decoder panel shows and why each line matters
The header panel prints the decoded header object, then the format label, the specification it belongs to, the library that parsed it, and the reason the detector chose that format. Those four lines matter more than they look. When a token is ambiguous, the reason tells you which structural signal won: the number of segments, the CBOR tag, the presence of a typ claim, the prefix bytes of a binary format. Without that line you are guessing why the tool disagreed with your expectation.
The payload panel prints the claims as they were signed. Nothing is recalculated. If the payload says the token expires at a certain second, the decoder repeats that number, converts it to a readable timestamp, and marks it as expired or still valid against your current clock. It does not tell you whether the issuer meant it.
The signature panel shows the raw signature bytes and hides them behind a reveal control, because a screenshot of a signed token is a leaked token. Copy buttons exist for all three panels. Pasting a token into a ticket, a chat, or a screenshot hands over a working credential until it expires, and sometimes after, if the service does not check expiry.
Validation status is the last line and the one most often misread. In decode mode it reports structure, not authenticity: the token parsed, the claims were readable, the timestamps were consistent with the current time. A green result here means the text is well formed. It does not mean the token was issued by your server.
Registered claims and the decisions they drive
The payload carries claims. Seven of them are registered, and each has a defined type and meaning. A decoder shows them together so you can see whether the set makes sense as a whole.
| Claim | Type | Meaning | Typical validation |
|---|---|---|---|
| iss | string | Issuer of the token | Must match the expected issuer exactly |
| sub | string | Subject, usually a user id | Must identify the principal the request acts for |
| aud | string or array | Intended audience | Must contain this service |
| exp | number | Expiry, Unix seconds | Reject when now is greater |
| nbf | number | Not valid before, Unix seconds | Reject when now is smaller |
| iat | number | Issued at, Unix seconds | Reject when in the future beyond skew |
| jti | string | Unique token id | Used for replay detection and revocation lists |
Custom claims are allowed and common: roles, scopes, tenant ids, plan names. They are not standard, so no validator will check them for you. A decoder shows them plainly, which is how most mistakes are found. A role stored as a string in one service and as an array in another survives until the second service reads it.
Two practical notes. Timestamps are seconds, not milliseconds, so a value with thirteen digits is almost always a bug in the issuer. And claim names are case sensitive: Exp is not exp, and a validator that looks for the lowercase form will treat a token with Exp as having no expiry at all.
Decoding is not verification, and the difference is the whole point
A decoder reads. A verifier checks. The signature is an HMAC or a public key signature over the first two segments, and checking it requires the shared secret or the public key. Without that key, the third segment is opaque bytes, and any text editor can produce a token with three segments and a plausible payload.
This matters because decoders are often used as a first step in an incident, and the instinct is to trust the green tick. The correct reading of a decoded token is: this is what the client sent. Whether the client was entitled to send it is a separate question, answered only by verifying the signature, then checking exp, nbf, iss and aud against the service's own expectations.
The verification order matters too. Verify the signature first, then the claims. Claim checks on an unverified payload are theatre: an attacker who can rewrite the payload can also set exp to next year and iss to your own issuer string.
The alg field, none, and key confusion
The header contains alg, and implementations that trust it hand the attacker control of the verification path. Two failure modes are old enough to be textbook, and both still appear in audits.
The first is alg: none. The token carries no signature, and a verifier that reads the algorithm from the header accepts it as valid. The fix is to never accept an algorithm you did not configure. The Verify mode of this tool is built that way: you pick the algorithm from a list, and the token's own claim is compared against your choice instead of steering it.
The second is confusion between symmetric and asymmetric families. A service signs with RS256 and publishes its public key. The attacker changes alg to HS256 and signs the forged token using the public key as the HMAC secret. A verifier that picks the algorithm from the header will now validate the forgery with a key everyone can read. The defence is the same: pin the algorithm per issuer, and reject a token whose header disagrees.
A third variant abuses kid. If the header's key id is used to build a file path or a query without sanitisation, the attacker can point verification at a key they control, or at a path traversal that loads a predictable file. Treat kid as an index into a fixed set of known keys, never as a filename.
Five-part tokens: what JWE hides and what stays visible
When the string has five segments, the payload is encrypted. There is no readable claims object, and no decoder can invent one. What remains visible is the protected header (algorithm and encryption method), the encrypted key, the initialisation vector, the ciphertext and the authentication tag.
That is still useful. The header names the key management algorithm, which tells you whether the token was wrapped with RSA-OAEP, AES key wrap, or a direct shared key. The tag length tells you whether the AEAD parameters match your configuration. Most JWE incidents are configuration mismatches, and those are visible without the key.
If someone asks you to "decode" a JWE and expects to see claims, the honest answer is that the content requires the decryption key. A tool that shows plaintext here either has the key or is fabricating output.
Beyond JWT: SD-JWT, CWT and the rest of the registry
Not every token is a JWT, and treating them all as one is the source of many wrong conclusions. A decoder that recognises families saves the round trip.
SD-JWT, used in verifiable credentials, keeps a normal JWT plus a list of disclosures and a key binding. Decoded, it shows the issuer-signed claims, the disclosure strings, and which claims are selectively disclosed. Reading it as a plain JWT shows the signed payload but hides the point: some claims are deliberately withheld.
CWT is the CBOR equivalent of JWT. It is binary, prefixed by a CBOR tag, and its claims use integer keys, so number 4 means exp and number 6 means iat. A decoder that maps those integers back to names is the difference between a readable claims table and a wall of numbers. The EAT and Token Status List profiles reuse CWT with a typ claim that identifies them, and without that mapping all three look identical.
PASETO, Macaroon, Biscuit, Branca, Fernet and SAML each have their own structure and their own security model. The common mistake is assuming that because a string is long and dot-separated it is a JWT. It is not, and the fields that look like claims are usually something else.
Expired tokens, clock skew and the debugging order
An expired token is the most common support ticket in any system that uses JWTs, and the cause is usually one of three things.
The first is a clock difference between issuer and verifier. A token issued at second N and presented to a server whose clock is forty seconds ahead looks future-dated, and a strict nbf check rejects it. The standard allowance is a small skew, commonly thirty to sixty seconds, applied to exp, nbf and iat consistently.
The second is caching. A client keeps a token past its expiry because nothing told it to refresh, and the retry loop hides the real error behind a generic 401.
The third is a units mistake. A token with exp in milliseconds expires in the year fifty thousand, so nobody notices the bug until a clock comparison overflows.
The debugging order that resolves these quickly: decode, read the three timestamps as absolute times, compare them with the server clock, then verify the signature with the configured algorithm. Decode first tells you which of the three problems you have before you touch any code.
What to check before shipping a token flow
A short list, in the order failures usually appear. Pin the accepted algorithms per issuer and reject anything else. Validate iss, aud, exp and nbf after the signature, never before. Keep tokens short-lived and treat refresh tokens as a separate credential with its own storage rules. Never log a full token: log the jti and the subject. Treat kid as an index into a fixed key set. And when something looks wrong, decode before you theorise: the header, the claims and the timestamps answer most questions in ten seconds.
Frequently Asked Questions
?Can I decode a JWT without the secret key?
Yes. The header and payload are base64url, not encrypted, so decoding needs no key at all. Only signature verification requires the secret or the public key.
?Does a decoded token mean the token is valid?
No. Decoding proves the string is well formed. Validity comes from verifying the signature and checking the claims against what the service expects.
?Why does my token have five parts instead of three?
Five parts mean JWE, an encrypted token. The payload is ciphertext and cannot be read without the decryption key; only the protected header, encrypted key, IV, ciphertext and tag are visible.
?Why is my token already expired when I just created it?
Almost always a units or clock problem. exp is in seconds, so a millisecond timestamp lands far in the future, and a clock difference of under a minute can make a fresh token look future-dated to a stricter server.
?What does alg none mean and is it dangerous?
It means the token carries no signature. It is dangerous only if the verifier reads the algorithm from the header; a verifier that pins its own algorithm rejects it outright.
?Can a JWT be revoked before it expires?
Not by itself. A JWT is a bearer credential valid until exp. Revocation requires server state: a denylist keyed by jti, short lifetimes with refresh, or a session record checked on each request.
?Is it safe to paste a token into an online decoder?
Only if the decoder runs locally in your browser, as this one does. A decoder that posts the token to a server has just received a working credential, and tokens in shared logs or tickets are equally exposed.
?What is the difference between a JWT and a JWE?
A JWT is signed and readable: anyone can see the claims, and the signature proves who issued them. A JWE is encrypted: the content is hidden, and only the intended recipient with the right key can read it.
?Why do some claims show as numbers in CWT tokens?
CWT uses CBOR with integer claim keys, so 4 is exp and 6 is iat. A decoder maps those integers to the names you expect; without that mapping the claims look like an unrelated key-value dump.
Albuquerque, NM, US · SO reputation 7991 · Badges: 5🥇44🥈54🥉 · SO member since 2011