JWT Structure Explained: Header, Payload, and Signature
What Is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe string that carries information between two parties. Defined in RFC 7519, JWTs are the backbone of modern authentication and authorization flows.
A JWT looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
That long string is three separate parts joined by dots. Each part serves a distinct purpose.
The Three Parts of a JWT
Every JWT follows the same structure:
header.payload.signature
| Part | Encoded With | Purpose |
|---|---|---|
| Header | Base64URL | Identifies the token type and signing algorithm |
| Payload | Base64URL | Carries the actual data (claims) |
| Signature | Base64URL | Ensures the token has not been tampered with |
Let's break down each one.
The Header
The header tells the recipient how to process the token. It contains two fields:
{
"alg": "HS256",
"typ": "JWT"
}
Header Claims
| Claim | Description | Common Values |
|---|---|---|
alg | Algorithm used to sign the token | HS256, RS256, ES256 |
typ | Token type | JWT |
alg (Algorithm) — This is the most critical field. It tells the verifier which algorithm was used to create the signature. Common choices include:
- HS256: HMAC with SHA-256 (symmetric — same secret for signing and verification)
- RS256: RSA with SHA-256 (asymmetric — private key signs, public key verifies)
- ES256: ECDSA with SHA-256 (asymmetric — smaller signatures than RSA)
typ (Type) — Almost always set to JWT. Some implementations omit it, but including it is a best practice for clarity.
The header is then Base64URL-encoded:
Base64URL({"alg":"HS256","typ":"JWT"})
→ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
The Payload
The payload contains the claims — statements about an entity (typically a user) and additional metadata. Claims come in three categories:
Registered Claims (Standard)
These are reserved claim names defined by RFC 7519:
| Claim | Full Name | Description |
|---|---|---|
iss | Issuer | Who issued the token |
sub | Subject | The principal the token is about (usually a user ID) |
aud | Audience | Intended recipient of the token |
exp | Expiration | Time after which the token is no longer valid |
nbf | Not Before | Time before which the token is not valid |
iat | Issued At | Time the token was issued |
jti | JWT ID | Unique identifier for the token (prevents replay) |
All time-based claims use Unix timestamps (seconds since epoch).
Example Payload
{
"sub": "1234567890",
"name": "John Doe",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
subidentifies the usernameandroleare custom claimsiatrecords when the token was issuedexpsets the token to expire 1 hour after issuance
Public and Private Claims
- Public claims: Registered in the IANA JSON Web Token Registry to avoid collisions (e.g.,
email,roles) - Private claims: Custom names agreed upon by the parties (e.g.,
department,plan_id)
Always avoid collision by choosing claim names that won't conflict with registered claims.
Important: Payload Is Not Encrypted
The payload is Base64URL-encoded, not encrypted. Anyone who intercepts the token can read its contents. Never store sensitive data (passwords, API keys, SSNs) in a JWT payload.
The Signature
The signature verifies that the token hasn't been altered. It is computed from the header and payload:
HMAC (HS256) Example
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
RSA (RS256) Example
RSASHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
privateKey
)
The signature ensures integrity — if anyone changes even a single character in the header or payload, the signature will no longer match.
Verification Flow
1. Split the JWT by dots → [header, payload, signature]
2. Recompute the signature using header + payload + secret/key
3. Compare the recomputed signature with the received signature
4. Match → token is valid and untampered
5. No match → token has been modified → reject
Base64URL Encoding Explained
JWTs use Base64URL encoding, not standard Base64. The differences matter:
| Standard Base64 | Base64URL | Reason |
|---|---|---|
+ | - | + is interpreted as space in URLs |
/ | _ | / is a URL path separator |
= padding | Removed | = is percent-encoded in URLs |
| Output not URL-safe | Output is URL-safe | JWTs travel in URLs and cookies |
Manual Decode Example
function decodeBase64Url(str) {
// Restore standard Base64
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
// Restore padding
while (base64.length % 4) base64 += '=';
return JSON.parse(atob(base64));
}
const header = decodeBase64Url('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9');
// { alg: "HS256", typ: "JWT" }
How the Three Parts Work Together
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ HEADER │ . │ PAYLOAD │ . │ SIGNATURE │
│ (algorithm) │ │ (claims) │ │ (proof) │
└──────────────┘ └──────────────┘ └──────────────┘
↓ ↓ ↓
Base64URL Base64URL HMAC/RSA of
encoded encoded header.payload
- The header tells the verifier which algorithm to use
- The payload carries the claims the application needs
- The signature proves the first two parts were issued by a trusted source
If any part changes, the signature breaks — and the token is rejected.
Common JWT Myths
Myth: JWTs Are Encrypted
Reality: JWTs are encoded, not encrypted. Anyone can decode the header and payload without a secret key. Use JWE (JSON Web Encryption) if you need confidentiality.
Myth: You Cannot Trust a JWT Without a Database Lookup
Reality: With asymmetric algorithms (RS256), the verifier only needs the public key — no database call needed. This is the key advantage of self-contained tokens.
Myth: Longer Secrets Mean Better Security
Reality: With HMAC, yes — use at least 256-bit secrets. With RSA or ECDSA, key length matters differently. Follow the algorithm's best practices, not guesswork.
Related Guides
Try It Yourself
Want to see the structure of a real JWT? Paste any token into our free JWT Decoder to instantly inspect its header, payload, and claims — all processed securely in your browser with no data sent to any server.