How to Decode and Debug a JWT (Without Guessing)
You have a token, an API returning 401, and no useful error message. Somewhere in that string of characters is the reason, and it is almost always one of six things.
A JWT is not encrypted. Every claim inside it is readable in seconds with no key and no special tooling, which is exactly why debugging one is straightforward once you know what to look at.
This guide covers how to read a token segment by segment, what each standard claim actually does, the six reasons tokens get rejected, and the one distinction that separates a working auth implementation from a broken one.
The three segments
Every JWT has the same shape: three base64url segments joined by dots.
The header declares how the token was signed. It is almost always two fields: alg for the signing algorithm and typ for the token type.
The payload holds the claims: the actual statements the token is making about who the bearer is and what they can do.
The signature is a cryptographic hash of the first two segments computed with a secret key. It is the only part that provides any security, and it is the only part you cannot inspect without the key.
The critical property, and the one most often misunderstood: the header and payload are encoded, not encrypted. Base64url is a transport encoding with no secrecy whatsoever, as covered in our guide on what base64 encoding is and is not. Anyone holding the token can read every claim in it.
The signature does not hide the contents. It proves they have not been altered.
This is why you must never put anything confidential in a JWT payload. Not a password, not a full date of birth, not an internal database connection string. Assume every claim will be read by whoever holds the token, because it will be.
Reading the payload: the registered claims
RFC 7519 defines seven standard claim names. You will see these constantly, and knowing what each does tells you most of what a token is asserting.
| Claim | Name | What it means |
|---|---|---|
| `iss` | Issuer | Who created and signed the token |
| `sub` | Subject | Who the token is about, usually a user ID |
| `aud` | Audience | Who the token is intended for |
| `exp` | Expiration | Unix timestamp after which it must be rejected |
| `nbf` | Not before | Unix timestamp before which it must be rejected |
| `iat` | Issued at | Unix timestamp of creation |
| `jti` | JWT ID | Unique identifier, used to prevent replay |
Three of these do the heavy lifting in practice.
`exp` is the one that fails most often. It is a Unix timestamp, seconds since 1 January 1970, and it is checked on every request. A value of 1784559600 means nothing to a human eye, which is why an expired token frequently looks like an inexplicable 401 rather than an obvious timeout.
`aud` catches tokens used in the wrong place. A token issued for api.example.com presented to admin.example.com should be rejected even though it is otherwise valid and unexpired. This is a real security control, not bookkeeping, and a mismatched audience is a common cause of failures in microservice architectures.
`iss` tells you which system minted it. In an environment with several identity providers, this is how you know which key to verify against.
Everything beyond these seven is a custom claim. name, email, role, permissions, and scope are all conventions rather than standards, and their meaning is whatever your system decided.
ā Use our free JWT Decoder at GlobalUtilityHub to read the header and payload of any token. It runs entirely in your browser and, by design, does not verify the signature. No sign-up needed.
Decoding a token by hand
Take this token, which is a real one signed with the secret secret:
Step 1: split on the dots. Three segments, 36 characters, 168 characters, and 43 characters.
Step 2: base64url decode the first segment.
Signed with HMAC using SHA-256 and a shared secret. Note this for later, because the algorithm determines how verification works.
Step 3: decode the second segment.
Step 4: convert the timestamps. This is the step people skip, and it is usually where the answer is.
iat 1784556000 is 20 July 2026 at 14:00:00 UTCexp 1784559600 is 20 July 2026 at 15:00:00 UTCA one hour lifetime. If the current time is past 15:00 UTC on that date, the token is expired and every correctly implemented API will reject it regardless of anything else in the payload.
Step 5: leave the signature alone. You cannot check it without the secret, and you should not try. Its presence tells you nothing about validity.
Notice the signature segment contains - and _. Those characters do not exist in standard base64, which is how you can tell at a glance that a decoder failing on a JWT is probably using the wrong variant.
Six reasons your token is rejected
1. It has expired. Decode the payload, convert exp, compare to now. This is the first thing to check and the answer more often than not. Fix: request a new token, and if it is expiring faster than expected, check the lifetime your issuer is configured for.
2. The audience does not match. The aud claim names a recipient, and the API compares it against its own identifier. A token minted for one service and presented to another is correctly refused. Typical error text: invalid audience or jwt audience invalid.
3. Clock skew between servers. If the issuing server's clock runs a few seconds ahead, a token can arrive with an iat or nbf in the receiving server's future and be rejected as not yet valid. Most libraries allow a tolerance, often defaulting to zero. Typical error: jwt not active. Fix: enable a small leeway, commonly 30 to 60 seconds, and run NTP.
4. Wrong verification key. The token was signed with one key and is being verified against another. Common after a key rotation, or when staging and production configs get crossed. Typical error: invalid signature. The token decodes perfectly, which makes this confusing: everything looks right because everything except the signature is right.
5. Algorithm mismatch. The header says RS256 but your verification code is configured for HS256, or vice versa. Typical error: invalid algorithm. Always check the header's alg against what your verifier expects.
6. The token is malformed. Fewer than two dots, whitespace introduced by copy and paste, a truncated segment, or padding that a strict decoder rejects. Typical error: jwt malformed or invalid token. Count the dots first: exactly two, no more, no fewer.
A practical order for working through these: decode the payload, check exp, check aud, check the header's alg, then look at key configuration. Five checks, and the first two account for most cases.
Decoding is not verifying
This is the distinction that matters more than everything above combined.
Decoding reverses base64url. It requires no key, takes microseconds, and can be done by anyone holding the token. It tells you what the token claims.
Verifying recomputes the signature using the secret key and compares it to the third segment. It requires the key, and it is the only thing that tells you whether those claims can be trusted.
A token that decodes successfully has told you nothing about its validity. An attacker can craft a token containing "role": "admin" that decodes perfectly, because encoding is not a security control. Only signature verification catches it.
The practical rule: never make an authorisation decision based on a decoded payload. Verify first, then read the claims. If your server code reaches into a payload before checking the signature, you have an authentication bypass regardless of how correct everything else looks.
Two related pitfalls worth knowing by name.
The `alg: none` problem. The JWT specification permits an algorithm value of none, meaning unsigned. Libraries that honoured this without an explicit opt-in could be handed a token with the signature removed and the header rewritten, and would accept it. Modern libraries reject none by default. Confirm yours does, and always configure your verifier with an explicit expected algorithm rather than trusting the header to tell you.
Algorithm confusion. Where a system uses RS256, verification uses a public key, and that key is by definition public. If a verifier can be induced to treat the token as HS256 instead, it may use that public key as the HMAC secret, which the attacker also has. The defence is the same: pin the expected algorithm in your verification configuration rather than reading it from the token you are trying to validate.
And a note on tooling. Any online decoder that processes your token on a server has received your token. For a public API response that is harmless. For a production token carrying real session authority it is a credential leak. Use a decoder that runs in your browser, or decode locally. Our decoder is client-side for exactly this reason, and it still is not the right place for a live production token.
The bottom line
A JWT is three base64url segments. The first two are readable by anyone, the third is the only part carrying any security, and the gap between those two facts is where most JWT bugs and every JWT vulnerability lives.
To debug: split on dots, decode the payload, convert exp from Unix time, then check aud and the header's alg. That sequence resolves the large majority of unexplained 401s.
To build safely: verify the signature before reading a single claim, pin the expected algorithm rather than trusting the header, and keep nothing confidential in the payload.
Our JWT Decoder reads the header and payload in your browser in under 30 seconds. Try it free at globalutilityhub.com/dev-tools/jwt-decoder/
Use our free JWT Decoder to apply what you have learned.
Open JWT Decoder ā