What's Actually Inside a JWT? Header, Payload, and Signature Explained

5 min read

A JWT (JSON Web Token) looks like a wall of random characters, but it's really three plain, structured pieces glued together with dots. Once you know what each piece is, a JWT stops being a black box and becomes just another data format you can reason about.

The three-part structure

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

  └──────────── header ────────────┘ └──────────── payload ───────────┘ └────── signature ──────┘

Each of the three segments is separately base64url-encoded (Base64's URL-safe variant — see our Base64 explainer for how that encoding works), and joined with . characters.

1. The header

Decodes to a small JSON object describing the token itself:

{
  "alg": "HS256",
  "typ": "JWT"
}

alg names the signing algorithm (HMAC-SHA256 here), and typ confirms this is a JWT. Nothing in the header is secret — it's plain, readable JSON the moment it's decoded.

2. The payload

The actual data — called claims. A mix of registered claims with defined meanings and whatever custom fields the issuer wants to include:

{
  "sub": "1234567890",
  "name": "Alice",
  "iat": 1721606400,
  "exp": 1721610000
}
  • sub — subject, usually the user ID this token represents.
  • iat — issued-at, a Unix timestamp for when the token was created.
  • exp — expiration, a Unix timestamp after which the token should be rejected.
  • name — a custom claim; anything beyond the registered set is fair game for whatever the issuing system needs.

This is the part worth being deliberate about: because the payload is just base64url-encoded JSON, it is readable by anyone who has the token, not encrypted. Don't put anything in a JWT payload you wouldn't be comfortable displaying in plain text — passwords, secrets, and sensitive PII don't belong here.

3. The signature

This is where the actual security lives. The signature is computed by taking the encoded header and payload, and running them through the algorithm named in the header, together with a secret (or private key) only the issuing server knows:

signature = HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secretKey
)

Anyone can decode the header and payload without knowing the secret — that's just base64. But nobody can produce a valid signature for a modified payload without that secret. This is the entire trust model of a JWT: the payload is public, but only the holder of the signing key can vouch for its authenticity.

Decoding vs. verifying — a critical distinction

Decoding a JWT just means base64url-decoding the header and payload — no secret required, which is exactly what our JWT Decoder does. Verifying a JWT means recomputing the signature with the correct key and checking it matches — that requires the secret, and should only ever happen server-side, not in a browser tool. A decoded token you haven't verified could have a payload that's been tampered with; never trust claims from an unverified token for anything security-sensitive.

Try JWT Decoder now

Inspect JWT header, payload, and signature with syntax highlighting.

Open JWT Decoder