JWT Decoder Explained: How to Inspect and Debug JSON Web Tokens
A login stops working, an API starts returning 401 Unauthorized, and all you have is a long opaque string made of three base64-ish segments separated by dots. That string is a JWT — JSON Web Token — and almost everything you need to know about the failed session is inside it. You just need to read it.
This guide explains what a JWT actually contains, how to decode one safely for inspection, and what to look for when authentication breaks.
The problem: tokens are unreadable by design
A JWT is not encrypted — it is encoded. The payload is plain JSON wrapped in Base64URL so it can travel safely inside HTTP headers. Yet when it arrives in your logs, it looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJhZHJlbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJVadQssw5cPasting that into a terminal and trying to echo your way through it rarely works, because JWTs use Base64URL encoding (with - and _ instead of + and /) and strip the padding characters. Standard Base64 tooling will silently mis-decode the last segment. Developers end up copy-pasting segments one by one, guessing where the header ends and the payload begins.
The solution: decode the token, inspect the claims
A JWT has three parts, each separated by a dot:
| Segment | Contents |
|---|---|
| Header | The signing algorithm (alg) and token type (typ) |
| Payload | The claims: subject, issuer, expiration time, custom data |
| Signature | Cryptographic proof that the token was not modified |
Decoding reveals all of this immediately. The fastest route is a purpose-built tool — the JWT Decoder & Parser on DigDevBox splits the token into its three segments, decodes the header and payload as formatted JSON, and lays out the registered claims so you can read them at a glance.
Because the tool runs entirely in your browser, the token never leaves your machine — an important property when you are inspecting real production tokens that contain user identifiers or session data.
Tool walkthrough: reading a broken token
Here is a debugging session in practice. An API rejects a token with 401 Token expired. Paste the token into the JWT parser and look at the payload:
{
"sub": "user-8421",
"iss": "https://auth.example.com",
"iat": 1759276800,
"exp": 1759363200
}2
3
4
5
6
Three registered claims answer the question instantly:
exp— the expiration timestamp. Convert it with the Unix Timestamp Converter to see exactly when the token died: epoch1759363200is a specific UTC second, not "yesterday some time".iss— who issued the token. If your service expects issuerauth.example.combut the token saysstaging-auth.example.com, you have found the bug.iat— issued-at. Comparingiatandexpshows the token's lifetime, which matters when you are tuning session duration.
The header is equally diagnostic. If it says "alg": "none", the token was not signed at all — a red flag worth stopping everything for. If your verifier expects RS256 but the header shows HS256, the mismatch explains the rejection before you touch a line of code.
When the payload contains nested data
Payloads often carry nested objects — roles, permissions, tenant identifiers. To inspect a deeply nested claim, copy the decoded payload into the JSON Formatter and re-format it with proper indentation; syntax errors in hand-edited claims also show up immediately.
And when a claim looks like another layer of encoding — an authorization code, a state parameter, part of a key — the Base64 string encoder/decoder handles the round trip in both directions.
Three failure modes that decode on sight
Most 401 mysteries fall into one of three patterns, and all of them are visible in the decoded token before you run anything:
Clock skew. The token's exp is still minutes away by your wall clock, but the API rejects it. Servers on different machines drift; if the issuer's clock runs ahead of yours (or an NTP correction just happened), a token can arrive pre-expired. Decoding shows you the exact second the two systems disagree about.
Audience mismatch. A token issued for one service (aud: "api.example.com") being replayed against another. APIs that check aud correctly will reject it, and the decoded payload tells you immediately which audience the token was actually minted for.
Algorithm confusion. The header announces HS256 while your verifier is configured for RS256 (or vice versa). This is also the shape of a known attack class — a token claiming alg: none, or an asymmetric algorithm expected where a symmetric one arrives — which is why the header is the first thing worth reading, not the last.
FAQ
Is decoding a JWT the same as verifying it? No. Decoding only reads the header and payload — anyone can do that, which is why a JWT must never contain secrets. Verification checks the signature against a secret or public key and is done by your auth library on the server.
Can I trust the claims inside a decoded token? Only after the signature is verified. Until then, treat the payload as an untrusted hint. Decoding is for debugging and diagnosis, not authorization decisions.
Why does my decoder fail on the third segment? The signature is binary data rendered as Base64URL — it will not decode into readable text. That is expected. Only the first two segments decode into JSON.
Does decoding a JWT expose my password? No password is ever stored in a well-designed JWT. But user IDs, emails and roles usually are — which is exactly why you should only decode tokens in tools that run locally in your browser.
How do I convert the exp claim to a readable date? Paste the epoch number into a timestamp converter. The date converter on DigDevBox shows it as UTC and local time in both directions.
More developer tools on DigDevBox
- JWT Decoder & Parser — inspect header, payload and registered claims
- JSON Formatter & Validator — format and validate decoded payloads
- Base64 string encoder/decoder — handle Base64URL round trips
- Unix Timestamp Converter — turn
expandiatinto real dates