DEV Community

Benjamin
Benjamin

Posted on • Originally published at jwt-base64-inspector.vercel.app

Your JWT payload is public. Read it before an attacker does.

Here's the sentence most developers learn a little too late: a JWT's header and payload are not encrypted. They're Base64. Anyone who can see your token can read every field inside it — your user ID, your role, your custom claims. The signature protects integrity, not confidentiality.

So before you store a secret in a token, let's be clear about what's actually readable. This is the same token your auth server hands out, decoded:

{"alg":"HS256","typ":"JWT"}                    <- header: the algorithm
{"sub":"1234567890","name":"Ada Lovelace",    <- payload: fully readable
 "iat":1516239022,"exp":1799999999}
Enter fullscreen mode Exit fullscreen mode

Both parts are plain JSON behind a Base64 wrapper. A kid with atob() and five minutes reads them. If your payload holds a password, an email, or a role that grants privileges — you just shipped a credential leak that your "encrypted" token vibes will never catch.

The claims that matter (and what they're for):

  • sub — who the token belongs to
  • iat — when it was issued
  • exp — when it dies (tokens without this never expire — a flaw)
  • iss / aud — who issued it and who it's for (your API)
  • jti — a unique ID, your best tool for revocation

HS256 vs RS256 — pick carefully.

HS256 signs with one shared secret. Whoever holds the secret can both verify and forge tokens. RS256 uses a public/private key pair — your server verifies with the public key, and only the private key can sign. For anything that crosses service boundaries, RS256. And if your library lets a token dictate its own algorithm, you've opened the classic alg confusion attack — lock the algorithm you accept, always.

The 12-point hardening checklist (abridged to the ones that matter):

  1. Never put secrets in the payload — it's readable by design.
  2. Always set exp and check it on every request.
  3. Pin the algorithm — never trust alg from the token.
  4. Validate aud and iss; most JWT libraries default to loose matching.
  5. Use RS256 (or better) for anything shared between services.
  6. Use short-lived access tokens + refresh tokens, not one token that lives for months.

That last one is the difference between a breach and an incident: a token that can be revoked and expires in 15 minutes is a credential. A token that lives for 90 days is a skeleton key.

I keep a full copy of this guide, with the complete 12-point checklist and every standard claim explained, at https://jwt-base64-inspector.vercel.app/guides/jwt-security. And if you want to see what your own tokens actually contain, decode one client-side — it never leaves your browser: https://jwt-base64-inspector.vercel.app

One honest caveat: this is the guide I wish I'd read before my first auth system, not after. Reading it after a production incident costs a lot more attention.

Which of the six checks above is the one most teams skip? I'd bet on aud validation — it's the quiet one that libraries don't nag you about.

Top comments (0)