How to Read a JWT (Safely) — What's Inside a JSON Web Token

If you’ve worked with a modern API, you’ve seen one: a long string that looks like eyJhbGci... split by two dots. That’s a JSON Web Token — a JWT — and despite the cryptic appearance, most of it is plain text you can read in seconds. Understanding the structure is the difference between treating auth as magic and actually debugging it.

The three parts of a JWT

Every JWT is three Base64URL-encoded pieces joined by dots:

header.payload.signature
  • Header — a small JSON object describing how the token is signed, e.g. {"alg":"HS256","typ":"JWT"}. The alg field tells you the signing algorithm.
  • Payload — the actual data, called claims. This is where you’ll find the user ID, expiry, issuer and any custom fields the API added.
  • Signature — a cryptographic stamp computed over the header and payload using a secret or private key. It’s the only part that isn’t human-readable, and it’s what makes the token tamper-evident.

The first two parts are encoded, not encrypted. Anyone can decode them. That’s an important and frequently misunderstood point: a standard JWT hides nothing. Decode the payload of any token you hold and you’ll read every claim in plain text. Drop one into the JWT decoder and you’ll see the header and payload instantly.

Reading the claims that matter

The payload uses short, standardized claim names. The ones you’ll check most often:

  • exp (expiration) — a Unix timestamp after which the token is invalid. This trips people up constantly, because it’s a raw number like 1718560800, not a readable date. Paste it into the timestamp converter to see exactly when the token dies.
  • iat (issued at) — when the token was created, also a Unix timestamp.
  • nbf (not before) — the token shouldn’t be accepted before this time.
  • iss (issuer) — who minted it (e.g. your auth provider’s URL).
  • sub (subject) — usually the user or entity the token represents.
  • aud (audience) — who the token is intended for.

Custom claims sit alongside these — roles, permissions, email, tenant IDs. If your API returns a 401 and you can’t see why, the answer is almost always in the payload: an expired exp, the wrong aud, or a missing role claim.

Why “alg: none” is a classic attack

Because the header declares its own algorithm, early JWT libraries had a dangerous flaw: if a token said "alg":"none", some servers accepted it without verifying any signature at all. An attacker could forge a payload, strip the signature, set alg to none, and walk in.

The lesson for anyone building auth: never trust the algorithm declared in the token. Your server should be configured to expect one specific algorithm and reject anything else. Decoding a token to inspect its header — which you can do on the JWT decoder — is exactly how you’d spot this kind of misconfiguration during testing.

The part everyone gets wrong: never paste a live token online

Here’s the critical safety point. A JWT is, in effect, a password. As long as it hasn’t expired, anyone who holds it can act as you against that API. So pasting a live production token into a random “JWT decoder” website is genuinely risky — that site now has a working credential, and you have no idea what it logs or stores.

Most online decoders send your token to a server to decode it. That’s unnecessary. Decoding Base64 and parsing JSON are things your browser does natively, with no network call required. The JWT decoder here runs entirely in your browser — the token never leaves your device, isn’t uploaded, and isn’t logged. You can confirm that yourself by opening your browser’s network tab while you decode: nothing goes out.

When in doubt with a production token: decode it locally, and if you must share an example, use a freshly minted throwaway token, not a real session.

Decoding by hand (so you trust the tool)

You don’t strictly need a tool at all. To prove the payload is just Base64:

  1. Take the middle section of the token (between the two dots).
  2. Base64URL-decode it — you can paste it into the Base64 decoder.
  3. You’ll get readable JSON.

JWTs use the URL-safe Base64 variant (- and _ instead of + and /, and padding often stripped), which is why a generic Base64 tool sometimes needs you to add = padding back. A purpose-built JWT decoder handles that automatically and pretty-prints the result.

The quick checklist

When a token misbehaves, work through this:

  1. Decode the payload — read the claims (JWT decoder).
  2. Check exp — convert it and confirm it hasn’t passed (timestamp converter).
  3. Check iss and aud — do they match what your API expects?
  4. Check the header alg — is it the algorithm your server is configured to accept?
  5. Verify the signature — separately, on your server, with the correct key. Decoding alone never proves a token is valid; only signature verification does.

A JWT stops being intimidating the moment you realize it’s a readable envelope with a tamper seal. Read the envelope freely — just never hand a live one to a stranger.