All posts

How to Decode a JWT and Read Its Claims

June 19, 2026 · DevTools

jwt
authentication
debugging
security

A JWT looks like gibberish until you realize it is just two JSON objects and a signature, each Base64url-encoded and joined by dots. Once you know that, decoding one by hand takes about a minute — and it is exactly what every "JWT debugger" tool does under the hood.

The fastest path is to paste your token into the JWT Decoder, but understanding the steps makes you confident the result is correct. Let's walk through it.

The structure

A JWT has three parts separated by dots:

<header>.<payload>.<signature>

Only the first two decode to readable JSON. The third is a binary signature — you verify it, you do not read it.

Step 1 — Split on the dots

Take your token and split it into three strings at every .:

header    = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
payload   = eyJzdWIiOiIxMjM0IiwibmFtZSI6IkRldkJlZSIsImV4cCI6MTcxODAwODQwMH0
signature = s9Kq5x...jQ

Step 2 — Base64url-decode the header

The header is standard Base64 in its URL-safe variant. Two differences from ordinary Base64:

  • - is used instead of +, and _ instead of /.
  • The = padding is stripped.

To decode with a standard decoder, undo those changes: replace -+, _/, then add = padding until the length is a multiple of 4.

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

The alg tells you how the signature was produced (HS256 = HMAC + SHA-256). You can experiment with the same primitives in the Base64 Encoder/Decoder.

Step 3 — Base64url-decode the payload

Same routine on the middle part:

eyJzdWIiOiIxMjM0IiwibmFtZSI6IkRldkJlZSIsImV4cCI6MTcxODAwODQwMH0
→ {"sub":"1234","name":"DevBee","exp":1718008400}

This JSON is the payload — the token's claims.

Reading the common claims

ClaimWhat it means
issIssuer — who minted the token
subSubject — usually the user or principal id
audAudience — the intended recipient
expExpiration (Unix seconds) — reject the token after this
nbfNot before — reject before this
iatIssued at
jtiToken id

In the example, exp: 1718008400 is a Unix timestamp. Convert it to a human date and you know exactly when the token stops being valid.

Remember: the payload is encoded, not encrypted. Anyone who reads the token off the wire can read these claims. Never place a secret here.

Step 4 — (Don't) decode the signature

The third part is a signature, not encoded JSON. Trying to Base64-decode it yields raw bytes. Its job is to let a recipient prove the header and payload were not tampered with — see our complete JWT guide for how verification works.

Decoding is not verifying

This is the trap. Decoding only requires the token itself — it tells you what the token claims. Verifying recomputes the signature with the secret (or public key) and checks exp, aud, and iss. Only a verified token should ever be trusted.

A token that decodes fine can still be forged or expired. Always verify on the server before acting on a claim. You can build and sign tokens locally with the JWT Signer to see how the signature ties everything together.

Quick recipe in JavaScript

function decodeJwt(token) {
  const [header, payload] = token.split(".");
  const decode = (part) => {
    // Restore standard Base64 from Base64url and add padding.
    const b64 = part.replace(/-/g, "+").replace(/_/g, "/");
    const padded = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
    return JSON.parse(atob(padded));
  };
  return { header: decode(header), payload: decode(payload) };
}

This is exactly the logic behind the JWT Decoder — minus the UI, validation, and the client-side-only guarantee that your token never leaves the page.

Tools mentioned in this post