What Decoding a JWT Doesn’t Prove: Claims vs Signature Verification

By · Published · Updated

A JWT has three parts separated by dots: header.payload.signature. The header and payload are Base64URL-encoded JSON, and you don't need a secret to read them. That's what a decoder shows you. The signature is the part that proves anything, and decoding doesn't check it.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0IiwiYWRtaW4iOnRydWV9.4pxTVi8...
└──── header ────┘  └──────── payload ────────┘  └── signature ──┘

Decode the middle segment and you get something like:

{
  "sub": "1234",
  "admin": true
}

Anyone with a text editor can produce that exact payload for any user ID, with "admin": true, and it will decode the same way. The only thing that stops your server from treating that forged token as legitimate is signature verification: checking the third segment against the secret (HS256) or public key (RS256) the server holds.

Advertisement

What a decoder is for

Debugging. You're looking at a token your own service issued, or one your API client received, and you want to see its claims (expiry, scopes, subject) without writing a script. That's a common, legitimate need. Deciding whether to trust the token is a different job. It belongs to signature verification on the server, using a library that does constant-time comparison and checks the algorithm field. Never let the alg claim in the token tell you which algorithm to verify with; that's the classic alg: none vulnerability class.

One gotcha with expiry

The exp claim is a Unix timestamp in seconds. Date.now() in JS returns milliseconds, so compare against Date.now() / 1000, or you'll think every token expired in 1970.

Advertisement

Try the JWT Decoder →

Related tools & guides

Part of JWT Tools & Guide