JWTs Explained: Header, Payload, Signature, and Why Decoding Isn't Verifying
A JSON Web Token (JWT) is a compact, URL-safe way to represent a set of claims, who a user is, who issued the token, when it expires, signed so a server can trust it came from the right place. It's readable by anyone who has it, and reading it proves nothing on its own.
Last updated: August 30, 2026
What's actually inside a JWT
A JWT is three base64url-encoded segments joined by periods: header.payload.signature. The header is a small JSON object naming the signing algorithm (alg) and usually the token type (typ: "JWT"). The payload is a JSON object holding the claims, the actual data the token carries. The signature is computed over the header and payload using the algorithm the header names, and it's what lets a server confirm the token wasn't altered after it was issued, provided the server actually checks it.
Why anyone can read a JWT
Base64url is an encoding, not encryption. It's the same kind of reversible transformation as base64, just using a URL-safe alphabet and no padding characters, so DNS names, URLs, and cookies can carry it without escaping. Decoding a JWT's header and payload takes no key, no secret, nothing beyond reversing that encoding. If a payload needs to stay confidential from whoever's holding the token, a plain JWT is the wrong tool. That's what the less common encrypted variant, JWE, is for.
The standard claims
RFC 7519 defines seven registered claim names, all optional, none required to be present: iss (issuer), sub (subject, usually the user ID), aud (intended audience), exp (expiration time), nbf (not valid before this time), iat (issued at), and jti (a unique token ID, useful for replay protection). The three time-based claims, exp, nbf, and iat, are all stored as NumericDate: the number of seconds since the Unix epoch, not milliseconds. Everything else in the payload is a custom claim the issuer defined for its own purposes.
Decoding versus verifying
Decoding reverses the base64url encoding and shows you the claims. Verifying checks the signature against the algorithm and key the header claims to use, and only verifying tells you whether the token is genuine. For HMAC algorithms like HS256, that means recomputing the signature with the same shared secret the issuer used. For RSA or ECDSA algorithms like RS256 or ES256, it means checking the signature against the issuer's public key. A tampered payload, or an entirely fabricated token, decodes exactly as cleanly as a legitimate one. Only signature verification tells the two apart.
The alg:none trap
RFC 7519 explicitly defines an "unsecured JWT": a token whose header sets alg to none and whose signature segment is an empty string. It has legitimate niche uses, but it's also a well-known attack: if a server's verification logic trusts whatever algorithm the incoming token's header claims to use, an attacker can take a legitimate token, change the header to alg: none, strip the signature, and edit the payload freely. A correct verifier expects a specific algorithm and rejects anything else, rather than trusting the token to describe its own security.
Common mistakes
- Trusting a decoded payload without verifying the signature first, especially on the server, where the whole point of a JWT is trust.
- Putting a claim's timestamp in milliseconds instead of the seconds NumericDate the spec expects, which silently produces the wrong expiration.
- Storing secrets or sensitive personal data in the payload of an unencrypted JWT.
- Accepting whatever
alga token's header claims rather than expecting one specific algorithm.
Paste a token to see its header, payload, and claim timestamps converted to readable UTC, entirely in your browser.
Frequently asked questions
Is a JWT encrypted?
No, not by default. A standard JWT (a JWS) is only base64url-encoded and signed, not encrypted. Anyone who has the token can decode the header and payload and read every claim in it. Encrypted JWTs (JWE) exist as a separate, less common format, but a typical JWT you'll see in the wild is readable by anyone who intercepts it.
Why does decoding a JWT not prove it's valid?
Decoding only reverses the base64url encoding on the header and payload, which anyone can do with no key at all. Proving the token is genuine means verifying its signature, which requires the secret (for HMAC algorithms like HS256) or the issuer's public key (for RSA/ECDSA algorithms like RS256 or ES256). A tampered or entirely fabricated token decodes exactly as cleanly as a real one.
What's the danger of the "none" algorithm?
RFC 7519 defines "alg": "none" as a valid way to create an explicitly unsecured token with an empty signature. It has legitimate uses, but a server that accepts any incoming token with alg:none as though it were verified has no real authentication at all, since anyone can set that header themselves. Verifiers are expected to reject alg:none unless a token was already known to be intentionally unsecured.
What should I put in a JWT payload?
Nothing you wouldn't want exposed to whoever holds the token. Since a standard JWT isn't encrypted, its payload isn't the place for passwords, secrets, or sensitive personal data. It's meant for claims: who the token is about, who issued it, and its validity window.