What is a JWT? Structure, claims and how to decode one safely

A JWT is three Base64URL segments joined by dots: header, payload and signature. The first two are encoded JSON, not encrypted, so anyone can read exp, sub and custom claims. Decode a token in your browser, never on a server you do not run, and never treat decoding as verification. No key is required to read one.

Last updated

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...
  1. Header. Metadata, typically {"alg":"HS256","typ":"JWT"}. alg names the signature algorithm. A verifier must allowlist algorithms; none is a legal alg that means “unsigned”.
  2. Payload. The claims. Registered ones (sub, exp, iat, iss, aud) plus whatever custom fields the issuer added (roles, tenant, email).
  3. 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. 1767225600 is 2026-01-01T00:00:00Z. Convert it with the Unix Timestamp Converter. If exp is 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.

Do it now, locally

Inspect header and payload of a JSON Web Token locally. Nothing is uploaded.

Open the JWT Decoder

Frequently asked questions

What is a JWT?

A JSON Web Token (RFC 7519) is three Base64URL segments joined by dots: header, payload and signature. The first two are encoded JSON, not encrypted, so anyone can read the claims. The third segment is proof, not a secret decoder ring.

Is it safe to paste a real token into a decoder?

Only if decoding happens in your tab. A live JWT is a bearer credential. The LocalFirstTools decoder never uploads the token; prefer expired or test tokens when you can, and never paste tokens into tools that send them to a server.

What are registered claims?

iss (issuer), sub (subject), aud (audience), exp (expiry), nbf (not-before), iat (issued-at) and jti (JWT ID). They are optional by the spec but almost every identity provider sets exp, iat and sub. Timestamps are Unix seconds.

Does decoding verify the signature?

No. Decoding Base64URL-decodes the header and payload. Verification needs the issuer's key and a library that checks the signature plus exp, iss and aud. A browser tool should not hold that key.

Why can I read a JWT without a key?

A signed JWT (JWS) is encoded, not encrypted. Base64URL is reversible. Confidential claims need an encrypted JWE (five segments), which no decoder can show without the key.

How is this different from the how-to-decode guide?

That guide is the mechanical steps. This one is the data model: structure, registered and custom claims, and the safety rules for inspecting a token. Use both; then inspect a sample in the JWT Decoder.