A JWT (JSON Web Token, RFC 7519) is a compact way to carry claims between two parties. It looks like xxxxx.yyyyy.zzzzz: a header, a payload and a signature, each Base64URL-encoded and joined by dots. The important fact is that the first two segments are encoded, not encrypted. Anyone who sees the token can read the claims. Paste a sample into the JWT Decoder or the payload inspector to see that for yourself; the token never leaves this tab. Decoding is not verification.
The three segments
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiI0MiIsImV4cCI6MTc2NzIyNTYwMCwiaXNzIjoiaHR0cHM6Ly9pZHAifQ . SflKxwRJ...
- Header. Metadata, typically
{"alg":"HS256","typ":"JWT"}.algnames the signature algorithm. A verifier must allowlist algorithms;noneis a legal alg that means “unsigned”. - Payload. The claims. Registered ones (
sub,exp,iat,iss,aud) plus whatever custom fields the issuer added (roles, tenant, email). - Signature. Bytes that prove the first two segments were not modified and were produced by someone who holds the key. It does not hide the payload.
Four dots (five segments) means an encrypted JWE. The payload is genuinely encrypted; no decoder can show it without the key. If a paste fails with “A JWT has three segments separated by dots”, count the dots before you blame Base64.
Registered claims you will actually see
iss— who issued the token (often an HTTPS issuer URL).sub— the subject, usually a user or client id.aud— the intended audience. An API should reject a token whose aud is someone else's API.exp— expiry, Unix seconds.1767225600is2026-01-01T00:00:00Z. Convert it with the Unix Timestamp Converter. Ifexpis in the past, a 401 is the server being correct.nbf— not before. A token used too early is also invalid.iat— issued-at, same unit as exp.jti— a unique id, used for revocation lists.
Custom claims are just more JSON keys. The usual debugging question is “did the identity provider actually add the claim my code reads?” Decode the payload and look. Nine times out of ten the claim is missing or named slightly differently (role vs roles, email vs preferred_username).
How to decode one safely
Safe means two things: you do not leak the token, and you do not treat decode as trust. The mechanical steps are in How to decode a JWT. In short: split on dots, Base64URL-decode the first two segments (swap -/_ back to +// if you use atob), then JSON.parse. Segments are Base64URL, not standard Base64; see Base64 vs Base64URL.
const [header, payload] = token.split(".");
const json = (seg) => JSON.parse(
atob(seg.replace(/-/g, "+").replace(/_/g, "/"))
);
json(header);
json(payload);Do this on your machine or in a client-side tool. A JWT in a server log, a support ticket or a “helpful” online debugger that posts the token is a credential you just shared. The decoder on this site runs as JavaScript in your tab: after the page has loaded, DevTools → Network should stay quiet when you paste.
What decoding does not do
It does not check the signature. It does not prove the token came from your identity provider. It does not prove exp will be honoured by the server (the server might use its own clock or ignore exp). It does not decrypt a JWE. Treat the output as “this is what the token says”, not “this token is valid”.
Verification belongs on the server, with a library (jose, PyJWT, java-jwt) and the issuer's JWKS or shared secret. Allowlist alg. Check iss, aud and exp. Never accept alg: none.
Next step: paste a test token into the payload inspector, then convert exp with the Unix → ISO preset.
A worked payload
This expired HS256 token is the one every JWT tutorial uses. It is safe to paste anywhere because it was never a live credential:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
The header decodes to {"alg":"HS256","typ":"JWT"}. The payload decodes to sub=1234567890, name=John Doe, iat=1516239022 (2018-01-18). There is no exp, so the token never expires unless the server invents a lifetime. That missing exp is itself a finding: a long-lived access token in a browser is a gift to anyone who copies it.
HS256 means “HMAC with SHA-256 and a shared secret”. RS256 and ES256 mean public-key signatures; the verifier uses a JWKS URL, not a password. The header's kid (key id), when present, tells the verifier which JWK to pick. None of that is required to read the payload. It is required to trust it.
Unencrypted JWTs should never carry passwords, session cookies you would not log, or government identifiers. If the claim is confidential, use JWE or do not put it in the token: look it up server-side from sub. If you must debug a production token, decode it locally, then rotate it. A screenshot of a JWT in Slack is a live credential until it expires.
Compare this overview with the step-by-step decode guide when you want the exact atob incantation, and with the jwt.io comparison when you need signature verification rather than a local read.