If you build anything with authentication, you decode JWTs constantly — to see why a login failed, what a token claims, when it expires. And almost everyone does it the same way: paste the token into the first “JWT decoder” that comes up in search. That habit is faster than it is safe, and it’s worth knowing why before your next copy-paste.

A JWT (JSON Web Token) is the little string that proves who you are on most modern apps — three chunks separated by dots: header.payload.signature. Here is the part that surprises people: the first two chunks aren’t encrypted. They are just Base64url-encoded JSON. Anyone holding your token can read everything in it in about two seconds.

What’s actually inside

Decode the payload and you get a plain JSON object of “claims” — the token’s contents:

  • sub — the subject, usually the user ID.
  • exp / iat / nbf — expiry, issued-at, and not-before, as Unix timestamps. exp is the one you check when a token “randomly” stops working.
  • iss / aud — who issued the token and who it’s meant for.
  • Whatever the app bolted on — roles, email, plan tier, and sometimes a lot more than the developer realized they were exposing.

How to decode one by hand

You don’t strictly need a tool. The payload is just Base64url, so you can read it in a browser console in one line:

JSON.parse(atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')))

That splits the token on dots, takes the middle segment, converts Base64url to standard Base64 (JWT uses the URL-safe - and _ characters), and parses the JSON. You’ll see the claims printed straight out.

Decoding is not verifying

The single most important thing to understand: reading a JWT tells you what it says, not whether it’s true. The signature — that third chunk — is what proves the token wasn’t forged, and checking it requires the issuer’s key. A decoder shows you the claims; it does not tell you the token is valid. Never treat a decoded payload as proof of anything on the server side.

The part the listicles skip: don’t paste live tokens into random sites

Here is the security angle the “top 10 JWT decoders” posts never mention. A production JWT is often a live session. Paste it into an unknown web decoder and you have just handed a stranger’s server a working credential — with no idea whether it’s logged, stored, or quietly replayed against your account. Most decoders are probably fine. “Probably” is a strange bet to make with the token that is your login.

That’s the unglamorous reason we built our own $1 JWT decoder: it splits out the header and payload, turns the exp/iat timestamps into real dates, flags an expired token — and doesn’t keep your token afterward. No account, no signup, nothing stored on someone’s server. A dollar for not gambling with a credential is a fair trade. The browser-console method above is free if you’d rather.

The quick checklist

  • Split on dots; the payload is the middle segment.
  • Base64url-decode it to read the claims.
  • Check exp first when a token suddenly stops working.
  • Remember that decoding is not verifying — the signature is where the trust lives.
  • Never paste a live token into a site you don’t trust.

A JWT is a postcard, not a sealed envelope: readable by anyone who holds it, trustworthy only if you check the signature. Decode them freely to debug — just remember what you’re actually holding when you copy one out of a request.

We build software that treats your credentials like credentials. That’s what we do at Rebel Studios.