JWT Structure Explained: Header, Payload, and Signature Deep Dive

Most JWT tutorials stop at "it has three parts". This one goes deeper. We will walk through every field of a real token: what alg, typ, and kid mean in the header, how registered claims differ from public and private claims in the payload, and what actually happens byte-by-byte when a token is signed with HS256 versus RS256. By the end, a decoded JWT should read like plain documentation to you.

The Big Picture: header.payload.signature

A JSON Web Token (defined in RFC 7519) is a compact, URL-safe way of transmitting signed claims between parties. Here is a complete example token, split for readability:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0yMDI2LTA3In0   <- HEADER
.
eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyXzQy
IiwiYXVkIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20iLCJleHAiOjE3ODM0MjU2
MDAsImlhdCI6MTc4MzQyNDcwMCwicm9sZSI6ImVkaXRvciJ9                  <- PAYLOAD
.
4lC7ZkFvQ2m8HxNwT1pRaU3sYbE9jKdOgWi5nMfB0cA                       <- SIGNATURE

Each of the first two parts is just base64url-encoded JSON. The third part is raw signature bytes, also base64url-encoded. Nothing is encrypted — a JWT is signed, not secret. Anyone can read it; only the key holder can produce a valid signature for it.

The Header: alg, typ, and kid

Decoding the header above gives:

{
  "alg": "HS256",
  "typ": "JWT",
  "kid": "key-2026-07"
}
  • alg — the signing algorithm. Common values: HS256 (HMAC-SHA256, symmetric), RS256 (RSA-SHA256, asymmetric), ES256 (ECDSA, asymmetric but with smaller keys and signatures). The dangerous value is none — a verifier must never accept it. Your server should hard-code the algorithms it accepts, not read them from the token.
  • typ — the token type, almost always JWT. Some systems use at+jwt for OAuth access tokens so verifiers can reject ID tokens sent where access tokens belong.
  • kid — the Key ID. When an issuer rotates keys, it publishes several public keys (usually at a JWKS URL like /.well-known/jwks.json) and stamps each token with the id of the key that signed it. The verifier picks the matching key by kid. Important: kid is a lookup hint into keys you already trust — never fetch or accept key material that the token itself supplies (that is what makes jku and x5u header injection attacks work).

The Payload: Registered, Public, and Private Claims

The decoded payload of our example:

{
  "iss": "https://auth.example.com",   // issuer  - who created the token
  "sub": "user_42",                    // subject - who the token is about
  "aud": "https://api.example.com",    // audience - who should accept it
  "exp": 1783425600,                   // expiry (unix seconds)
  "iat": 1783424700,                   // issued-at (unix seconds)
  "role": "editor"                     // private claim - app-specific
}

RFC 7519 groups claims into three categories:

  • Registered claims — standardized short names with defined meaning: iss, sub, aud, exp, nbf, iat, jti (unique token id, useful for revocation lists). All optional, but exp should be considered mandatory in practice.
  • Public claims — names registered in the IANA JSON Web Token Claims registry or namespaced with a URI to avoid collisions, e.g. email, name, or https://myapp.com/claims/tenant. These are meant to be understood across systems.
  • Private claims — anything two parties agree on privately, like role or plan above. Fine within your own ecosystem, but keep them small: every claim travels on every request, and an HTTP header over ~8 KB will be rejected by many proxies.

One rule cuts across all three categories: never put secrets in the payload. It is readable by anyone who obtains the token — no key required.

The Signature: HS256 vs RS256 Signing Flows

The signature is computed over the exact base64url text of the first two parts:

signingInput = base64url(header) + "." + base64url(payload)

HS256:  signature = HMAC-SHA256(secret, signingInput)
RS256:  signature = RSA-PKCS1-v1.5-Sign(privateKey, SHA256(signingInput))

The practical difference matters more than the math:

  • HS256 (symmetric) — one shared secret signs and verifies. Simple and fast, ideal when the same service issues and consumes tokens. Weakness: every service that can verify can also mint tokens, and a short human-chosen secret can be brute-forced offline.
  • RS256 (asymmetric) — the auth server keeps a private key; every other service verifies with the public key. Perfect for microservices and third-party integrations: verification ability never implies forging ability. Tokens and signatures are larger (an RS256 signature is 256 bytes for a 2048-bit key vs 32 bytes for HS256).
  • ES256 — the modern middle ground: asymmetric like RS256, but with signatures around 64 bytes. Increasingly the default for new systems.

Verification reverses the flow: the verifier rebuilds signingInput from the token it received, computes or checks the signature with its key, and rejects the token if a single byte of header or payload changed. That is the entire integrity guarantee of a JWT.

Walking Through the Example Token Field by Field

  • alg: HS256 — verify only with HMAC-SHA256 and the configured secret. If a token arrives claiming RS256 or none, reject it.
  • kid: key-2026-07 — the July 2026 signing key; the previous key can still verify old tokens during rotation overlap.
  • iss — must equal the issuer you expect. Prevents tokens from a different (even legitimate) auth server being accepted.
  • sub: user_42 — the stable user identifier. Use this, not email (emails change), as your foreign key.
  • aud — must contain your API's identifier. Prevents a token minted for service A being replayed against service B.
  • exp / iat — 1783425600 minus 1783424700 is 900 seconds: a 15-minute token, a healthy lifetime for an access token.
  • role: editor — a private claim your API can trust only after signature verification passes.

The fastest way to build this reading skill is repetition: paste tokens from your own apps into a client-side decoder and identify every field. The Dev Brains AI JWT Decoder decodes in your browser and highlights expiry so nothing sensitive leaves your machine.

Frequently Asked Questions

What are the three parts of a JWT?

A JWT has a header (JSON metadata such as the signing algorithm), a payload (JSON claims about the user and token), and a signature (cryptographic proof that the header and payload were not modified). The three parts are base64url-encoded and joined with dots.

What is the difference between HS256 and RS256?

HS256 is symmetric: the same secret key both signs and verifies the token, so every verifier can also forge tokens. RS256 is asymmetric: a private key signs and a public key verifies, so services can verify tokens without being able to create them. RS256 suits microservices and third-party verification.

What is the kid field in a JWT header?

kid means Key ID. It tells the verifier which key from a key set (usually a JWKS endpoint) was used to sign the token, which enables key rotation. The verifier must look up the key by kid from a trusted source, never accept a key embedded in the token itself.

Try the Free JWT Decoder

Decode any JWT's header, payload, and expiry instantly — 100% in your browser. No signup, no cost, and your token never leaves your machine.

Related articles