JWT header, payload, and signature explained - cover art

JWT and security 15 min read

JWT header, payload, and signature explained

May 29, 2026 · 15 min read

Every JWT is two JSON objects plus a MAC or digital signature. Developers who understand what each segment contains debug auth failures faster - especially when alg mismatches, clock skew breaks exp, or a gateway rewrites claims.

The header is small JSON, usually under 100 bytes. alg is required for signed JWTs and must match what the verifier expects - never accept none in production. typ is often JWT; nested tokens may use JWT with a cty (content type) of JWT for JWS inside JWE. kid (key ID) points verifiers at the right public key in a JWKS rotation.

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "2024-06-rotation-1"
}

Payload: registered, public, and private claims

Keep payloads lean. Large claim sets inflate every request header and may hit proxy limits. Put bulky authorization data in the database keyed by sub, not inside the token.

Base64URL encoding rules

JWT uses Base64URL: standard Base64 with - instead of +, _ instead of /, and padding = stripped. Decoders must accept unpadded input. A common mistake is piping a JWT through standard Base64 tools without URL-safe alphabet conversion, which yields garbage JSON.

function decodeJwtPart(segment) {
  const padded = segment.replace(/-/g, "+").replace(/_/g, "/");
  const json = atob(padded.padEnd(padded.length + ((4 - (padded.length % 4)) % 4), "="));
  return JSON.parse(json);
}

How the signature is built

For HMAC-SHA256 (HS256), the signing input is the ASCII string BASE64URL(header).BASE64URL(payload). HMAC-SHA256 is applied with the shared secret; the result is Base64URL-encoded as the third segment. For RS256, RSA-SHA256 replaces HMAC but the signing input string is identical. Verifiers recompute and compare in constant time.

signing_input = base64url(header_json) + "." + base64url(payload_json)

HS256: signature = base64url( HMAC-SHA256(signing_input, secret) )
RS256: signature = base64url( RSA-SHA256(signing_input, private_key) )

Inspecting real tokens safely

Copy tokens from browser devtools or API logs into a local decoder to see pretty-printed JSON and Unix timestamps for exp and iat. Redact production tokens before screenshots; even without the secret, payloads may contain emails and internal IDs.

FAQ

What happens if I change one character in the payload?
The signature no longer matches. Servers must reject the token with 401 Unauthorized unless they never verified signatures - a critical misconfiguration.
Can the header and payload use pretty-printed JSON?
No. JWT libraries serialize to compact JSON (no extra whitespace). Different whitespace changes the signature.
What is the jti claim for?
jti (JWT ID) is a unique token identifier useful for replay detection or one-time use tokens when stored in a cache.
Why does my payload decode but verification fail?
Decoding needs no key; verification needs the correct secret, public key, algorithm, and sometimes issuer/audience checks.

Related: What is a JWT · Decode JWTs safely

Browse all tools