Skip to main content
ToolstikToolstik
BlogDeveloper

JWT Header vs Payload vs Signature Explained

ToolstikUpdated 6 min read
Diagram of a JWT with its header, payload, and signature segments separated by dots

A JWT has three Base64URL-encoded parts separated by dots: the header says how the token is signed, the payload carries the claims (who the user is, when the token expires), and the signature proves the first two parts haven't been changed since the issuer signed them. The header and payload are readable by anyone, and you can view them in the Toolstik JWT Decoder. Only the signature, checked with the right key, makes the token trustworthy.

Try it now

JWT Decoder

Open full tool

Loading tool…

The shape of a token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxNzAwMDAzNjAwfQ.tulDXbfVqDWHrXTAOEEdlUfcIeFRjIRiBiMlcRqwA6Y

Split on the dots, that's:

header:    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
payload:   eyJzdWIiOiJ1c2VyXzEyMyIsImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxNzAwMDAzNjAwfQ
signature: tulDXbfVqDWHrXTAOEEdlUfcIeFRjIRiBiMlcRqwA6Y

Each segment is Base64URL: standard Base64 with + replaced by -, / replaced by _, and trailing = padding removed, so the token is safe in URLs and HTTP headers. You can reproduce this in the Base64 Encoder / Decoder by switching on URL-safe Base64 and clicking Decode on the first segment.

The formal names: this is a JWS (JSON Web Signature, RFC 7515) carrying a JWT claims set (RFC 7519). An encrypted token, a JWE (RFC 7516), has five segments instead and can't be read without the decryption key.

Part 1: the header

The header is a small JSON object describing the token:

{
  "alg": "HS256",
  "typ": "JWT"
}
FieldMeaning
algSigning algorithm, for example HS256, RS256, ES256, or EdDSA
typMedia type, usually JWT
kidKey ID, which tells the verifier which key from a set (often a JWKS endpoint) to use

Base64URL-encoding {"alg":"HS256","typ":"JWT"} gives eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. Because every JSON object starts with {", which encodes to eyJ, you can recognize JWTs and other Base64 JSON at a glance.

Security note: the header is attacker-controlled input. A verifier should decide which algorithms it accepts, not take alg from the token at face value. The classic failures are accepting "alg": "none" (no signature at all) and algorithm confusion, where an RS256 public key gets used as an HS256 secret.

Part 2: the payload (claims)

The payload is a JSON object of claims. Some names are registered in RFC 7519 and have defined meanings:

ClaimNameValue
issIssuerWho created the token, often a URL
subSubjectWho the token is about, usually a user ID
audAudienceWhich API or service should accept it
expExpirationUnix time in seconds after which it's invalid
nbfNot beforeUnix time in seconds before which it's invalid
iatIssued atUnix time in seconds when it was created
jtiJWT IDUnique ID, useful for revocation lists

Everything else is a custom claim: roles, tenant IDs, email, scopes.

The three time claims are plain integers of seconds since 1970-01-01T00:00:00Z, not milliseconds. "exp": 1700003600 means 2023-11-14T23:13:20Z. To read them, paste the number into the Unix Timestamp Converter. The guide on how to convert a Unix timestamp covers the seconds-versus-milliseconds trap that breaks so many expiry checks.

Two consequences of the payload being merely encoded:

  1. Anyone holding the token can read it. Don't put secrets, passwords, or sensitive personal data in claims.
  2. Size adds up. The token travels with every request, often in a header or cookie. Large claim sets make every call heavier, and some servers and proxies limit header size.

Part 3: the signature

The signature is computed over the encoded header and payload joined by a dot. For HS256:

signature = base64url( HMAC-SHA256( secret, base64url(header) + "." + base64url(payload) ) )

In Node.js, this reproduces the signature of the example token (the demo secret is demo-secret, never use that for real):

const crypto = require("crypto");
const [h, p, s] = token.split(".");
const expected = crypto.createHmac("sha256", "demo-secret").update(`${h}.${p}`).digest("base64url");
console.log(expected === s); // true

Change one character in the payload, such as a different sub or a later exp, and the expected signature no longer matches. That's the whole security model: integrity and authenticity, not secrecy.

Symmetric vs asymmetric algorithms

FamilyExamplesSigns withVerifies withGood for
HMACHS256, HS384, HS512Shared secretSame secretOne service that issues and checks its own tokens
RSARS256, PS256Private keyPublic keyIdentity providers and many verifying services
Elliptic curveES256, EdDSAPrivate keyPublic keySame as RSA, with smaller keys and signatures

With HMAC, every service that can verify can also mint tokens. With RSA or EC, you can publish the public key (often at a /.well-known/jwks.json URL) and only the issuer holds the private key.

How the three parts work together

  1. The issuer builds the header and payload, encodes them, signs them, and joins all three with dots.
  2. The client stores the token and sends it, typically as Authorization: Bearer <token>.
  3. The API splits the token, recomputes or checks the signature with its key, rejects disallowed algorithms, then checks exp, nbf, iss, and aud.
  4. Only after all of that does the API trust the claims.

A decoder, Toolstik's included, does only the split-and-read part of step 3. That's why the JWT Decoder shows a reminder that it doesn't verify signatures, and why it never asks for your secret.

The decoder's Copy header and Copy payload buttons give you the JSON as text, which is handy when you want to search a claim or paste exp into the Unix Timestamp Converter.

Putting it into practice

To inspect a real token step by step, including the safety checks before you paste and fixes for common decode errors, follow how to decode a JWT safely. Once the header and payload are on screen, you can copy the JSON into the JSON Formatter if you want to reformat it. The rest of the browser-based toolkit (JSON, Base64, URL encoding, timestamps, and regex) is collected in free developer tools online.

Frequently asked questions