You paste a token into a decoder, you get JSON back, and you move on. The
problem is what the page did not check before showing that JSON. We read our
own decoder line by line and ran the arithmetic to pin down exactly which
checks run, which values are trustworthy, and where the badge can lie to you.
The three-part rule is the first gate
A JSON Web Token is three base64url strings separated by dots:
header.payload.signature
Our decoder splits the pasted text on dots and rejects anything that does not
produce exactly three parts. Two parts, four parts, or text with no dots at
all triggers the invalid token card, which tells you the token must have
3 parts separated by dots. A second gate follows: the first two parts must
decode to text that JSON.parse accepts. Either failure shows the same error
card, so a token that splits correctly but contains a typo in the payload
still gets rejected.
This gate catches the two most common paste mistakes: copying only half of a
wrapped token from a log line, and pasting a session string that is not a JWT
at all.
Base64url is not base64, and padding decides the parse
JWT parts use a URL-safe alphabet. Plus becomes a dash and slash becomes an
underscore, so the token can travel in headers and URLs without escaping.
Standard atob rejects that alphabet, so the decoder converts it back and adds
the missing padding:
pad = (4 - (length % 4)) % 4
We verified the two real cases with the RFC 7519 example token. The header
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 is 36 characters. 36 modulo 4 is 0, so
zero padding characters are added. The payload
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
is 74 characters. 74 modulo 4 is 2, so two equals signs are appended before
atob runs. Skip that padding step and the second, longer half of the token
family fails to decode even though the token is valid.
What the header and payload actually prove
The decoder pretty-prints both parts as two-space indented JSON and shows two
badges above them.
The algorithm badge copies the alg field from the header. Treat it as a claim
written by whoever built the token. A header that says HS256 does not prove
the signature used HS256, and a header that says none is a claim you should
reject in server code regardless of what a decoder displays.
The expiry badge appears only when the payload contains exp and that value is
a number. The tool multiplies it by 1000, renders a local date, and compares
it against a captured timestamp. For a deterministic check, a payload with
exp set to 1000000000 renders as 2001-09-09 01:46:40 UTC and shows the
expired badge on any clock set after 2001. Numbers you can reproduce in one
paste.
Everything else in the payload, sub, iat, scopes, tenant ids, is displayed
as data. Readable is not the same as verified.
The signature is displayed, never checked
The third part appears as-is in base64url with a note that it cannot be
decoded without the secret. That note is the honest framing. The signature is
HMAC output over the first two parts. You can base64-decode those bytes, but
they carry no readable content, and without the signing secret you cannot
recompute and compare them. A browser decoder that shows a green checkmark on
an arbitrary pasted token is asserting something it did not test. Ours shows
claims, and stops there.
Verification belongs where the secret lives. Your server recomputes the
signature, checks the algorithm allowlist, and checks exp against its own
clock on every request.
The expiry badge can go stale, and that is a real bug class
The tool captures the current time once, in the state initializer that runs
when the page loads. Every expiry comparison for the life of that page view
uses that snapshot. Paste a token with exp ninety seconds in the future, read
"valid", leave the tab open for two minutes, and the badge still says valid.
The token is expired, the display is not.
The fix on the reader side is simple. Treat the badge as a hint and make your
own decision with a fresh timestamp. On our side it is a known defect worth
fixing with a periodic clock update.
Steps to inspect a token
1. Count the dots before you paste. Two dots means three parts. Any other
count will fail the gate.
2. Paste the token. A red invalid card means the split or the JSON parse
failed. Check for a truncated copy before you suspect the token.
3. Read the alg badge. Decide whether your server would accept that
algorithm.
4. Read the expiry badge against the stale-clock caveat above.
5. Copy the payload JSON with the copy button and check each claim you plan
to trust.
6. Remember the signature pane is informational. Do verification server-side.
Checklist before you trust what you read
- Token splits into exactly three parts on dots.
- Header and payload decode and parse as JSON.
- alg value is on your server allowlist.
- exp compared against a clock fresher than the page load.
- No authorization decision made from the decoded payload alone.
If you have a token shape that a decoder rejects but your backend accepts,
that mismatch is worth documenting. Test the three-part gate, the padding
arithmetic, and the expiry badge with your own sample tokens in the
JWT decoder at https://webrecast.com/en/jwt-decoder