Skip to content
Spellkit

What a JWT Actually Proves (and What It Doesn't)

A JWT's signature proves the token wasn't tampered with since it was issued — it says nothing about whether the claims inside were true when it was signed, or still are.

A JSON Web Token looks like a single opaque blob, but it's three separately meaningful parts, and understanding what each one actually guarantees explains both why JWTs are useful and where people misuse them.

The three parts

A JWT is three Base64URL-encoded segments joined by dots: header.payload.signature.

  • Header — metadata about the token itself, mainly which signing algorithm was used.
  • Payload — the actual claims: who the token is about (sub), when it was issued (iat), when it expires (exp), and any custom data the issuer wants to attach.
  • Signature — a cryptographic signature over the header and payload, computed with a secret (or private key) only the issuer holds.

The header and payload are readable by anyone

Because the header and payload are just Base64-encoded JSON — not encrypted — anyone holding the token can decode and read every claim inside it instantly, no key required. This surprises people who assume a JWT is like an encrypted credential; it's not. If a JWT contains anything genuinely sensitive in the payload, that data is exposed to anyone who gets hold of the token, which is exactly why sensitive data belongs in a database looked up by a claim, not embedded directly in the token.

What the signature actually proves

The signature is the only part offering any guarantee, and specifically: the header and payload haven't been modified since the token was signed, by someone who holds the signing secret. Verifying the signature confirms integrity and origin — not truth. If an issuer signs a token claiming "role": "admin" for a user who was an admin at the moment of signing, the signature will still verify successfully even if that user is demoted a minute later, right up until the token expires — because the signature only checks that the bytes match what was originally signed, not whether the claim is still accurate now.

Why exp (expiration) matters so much

Because a valid signature says nothing about whether the claims are still current, JWTs rely heavily on short expiration windows (exp) to limit how long a stale claim can be trusted. A JWT with no expiration, or an expiration set unreasonably far in the future, means any claim baked into it (permissions, roles) stays "provably signed" for that entire window regardless of whether it's still true — which is why revoking access before a long-lived JWT naturally expires generally requires a separate mechanism (a token blocklist, a shorter-lived token plus a refresh flow), since the JWT itself has no built-in way to be individually revoked.

The algorithm confusion attack

A once-common vulnerability: some libraries let the token's own header specify which algorithm to use for verification. An attacker could take a token normally signed with a strong asymmetric algorithm (RS256), switch the header to claim a symmetric algorithm (HS256), and sign it using the issuer's public key as if it were a shared secret — public keys are, after all, public. Poorly configured verifiers would use whatever algorithm the header claimed and successfully "verify" a forged token. Correctly configured systems fix the expected algorithm at verification time and never trust the algorithm named in the token itself — this is a configuration detail worth knowing rather than trusting a library's defaults blindly.

Reading, not trusting

Because the payload is just readable JSON, decoding a JWT to inspect its claims (for debugging, for understanding what a third-party API sent you) is completely safe and doesn't require the signing key. What does require care is validating one before trusting its contents in a real system — that means verifying the signature, checking exp/nbf, and confirming the algorithm, not just parsing the JSON out of the middle segment.

Spellkit's JWT Decoder splits a token into its header, payload, and claims, and flags whether it's expired, valid, or not yet valid based on its time claims — entirely in your browser, since a real-world JWT can carry claims you'd rather not send to a server.