Calculators Converters Generators Developer Tools Finance Tools Writing Tools SEO Tools
Blog About Contact

How to Decode and Debug a JWT (Without Guessing)

šŸ’” Quick Answer
A JWT is three **base64url** segments separated by dots: header, payload, signature. Anyone can decode the first two without a key. Only the holder of the secret can verify the third. **Decoding is not verifying**, and confusing the two is the most consequential JWT mistake.
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.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxYTJi...In0.zZEtvwbLdDvsV1CNI-ABSiXe8-BgY6oW01PVnd94IPk └──────────── header ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ └──── payload ā”€ā”€ā”€ā”€ā”˜ └──────────── signature ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

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.

ClaimNameWhat it means
`iss`IssuerWho created and signed the token
`sub`SubjectWho the token is about, usually a user ID
`aud`AudienceWho the token is intended for
`exp`ExpirationUnix timestamp after which it must be rejected
`nbf`Not beforeUnix timestamp before which it must be rejected
`iat`Issued atUnix timestamp of creation
`jti`JWT IDUnique 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:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxYTJiM2MiLCJuYW1lIjoiQWxleCBDaGVuIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwiYXVkIjoiYXBpLmV4YW1wbGUuY29tIiwiaWF0IjoxNzg0NTU2MDAwLCJleHAiOjE3ODQ1NTk2MDB9.zZEtvwbLdDvsV1CNI-ABSiXe8-BgY6oW01PVnd94IPk

Step 1: split on the dots. Three segments, 36 characters, 168 characters, and 43 characters.

Step 2: base64url decode the first segment.

{"alg":"HS256","typ":"JWT"}

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.

{ "sub": "1a2b3c", "name": "Alex Chen", "iss": "https://auth.example.com", "aud": "api.example.com", "iat": 1784556000, "exp": 1784559600 }

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 UTC
exp 1784559600 is 20 July 2026 at 15:00:00 UTC

A 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/

Written by Sandesh Dhulekar

Sandesh Dhulekar is the founder of GlobalUtilityHub. He designs and codes all tools on the site himself, tracing every calculation to published formulas and public datasets.

Last updated 20 July 2026
Ready to try it yourself?

Use our free JWT Decoder to apply what you have learned.

Open JWT Decoder →

Frequently Asked Questions

Header, payload, and signature, joined by dots. The header names the signing algorithm and token type. The payload carries the claims about the user and the token's validity window. The signature is a cryptographic hash of the first two segments computed with a secret key, and it is the only part providing security.
Yes. The header and payload are base64url encoded, which is a transport encoding with no secrecy. Anyone holding the token can decode and read every claim in it without a key. The signature prevents modification, not reading. Never put confidential data in a JWT payload.
Decoding reverses base64url to reveal the claims, requires no key, and proves nothing. Verifying recomputes the signature with the secret key and confirms the token has not been altered. A token can decode perfectly and still be forged. Authorisation decisions must always follow verification, never decoding alone.
Usually clock skew. If the issuing server's time is even slightly ahead of the verifying server's, a token can arrive with timestamps in the receiver's future and be rejected. Most libraries support a tolerance window, often defaulting to zero, so enabling a leeway of 30 to 60 seconds and running NTP on both servers resolves it.
It is a Unix timestamp, counted in seconds since 1 January 1970 UTC, after which the token must be rejected. A value like 1784559600 corresponds to 20 July 2026 at 15:00 UTC. Converting exp to a readable date is the single most useful debugging step for an unexplained 401.
JWTs use base64url, not standard base64. It substitutes - for + and _ for /, and typically strips the trailing padding. A standard decoder may error on those characters or reject the string as an invalid length. Use a decoder that handles base64url, or convert the characters and re-pad before decoding.
It depends entirely on where the decoding happens. A tool running in your browser never transmits the token. A server-side tool receives it in full. For a production token carrying real session authority, treat pasting it into any website as a credential disclosure. Decode locally or use a client-side tool, and prefer test tokens when debugging.
The JWT specification permits an algorithm value of none, meaning the token is unsigned. Libraries that accepted this without an explicit opt-in could be given a token with its signature stripped and its header rewritten, and would treat it as valid. Modern libraries reject it by default. Always configure your verifier with an explicit expected algorithm rather than trusting the header.