Understanding JSON Web Tokens (JWT): Debugging Payload Structure & Common Pitfalls
Try the Interactive Tool
Test and validate client-side with zero data uploads.
JSON Web Tokens (JWT), defined by **RFC 7519**, are an open standard for securely transmitting information between client applications and API servers as a compact, self-contained JSON object. They serve as the foundation for modern stateless authentication in microservices and OAuth 2.0 / OpenID Connect flows.
In this guide, we break down the anatomical structure of a JWT, explain public vs. private claims, and review critical security vulnerabilities developers encounter when implementing JWT verification.
1. The Anatomy of a JWT
A JWT is a single string composed of three distinct parts separated by dots ("."):
[Header].[Payload].[Signature]An example encoded token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cPart 1: Header
The header contains metadata regarding the token type and the hashing algorithm used to produce the signature (e.g., HS256 or RS256).
{
"alg": "HS256",
"typ": "JWT"
}Part 2: Payload (Claims)
The payload contains the statement claims regarding the user entity and additional metadata. Claims fall into three categories:
1. **Registered Claims:** Pre-defined standardized names:
* "iss" (Issuer): Who issued the token.
* "exp" (Expiration Time): Unix timestamp after which the token is invalid.
* "sub" (Subject): Unique user ID or entity identifier.
* "iat" (Issued At): Unix timestamp when the token was created.
2. **Public Claims:** Custom claims registered in the IANA JSON Web Token Registry or defined with collision-resistant names (e.g., URLs).
3. **Private Claims:** Bespoke custom fields agreed upon by system authors (e.g., "role": "admin").
{
"sub": "1234567890",
"name": "Alice",
"role": "system-administrator",
"iat": 1516239022,
"exp": 17516239022
}Part 3: Signature
The signature is created by hashing the Base64URL-encoded header, the Base64URL-encoded payload, and a secret key (for symmetric algorithms) or private key (for asymmetric algorithms):
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secretKey
)The server checks this signature upon receiving a request to ensure that the client or an attacker has not tampered with the header or payload contents.
2. Critical Vulnerabilities & How to Avoid Them
Vulnerability 1: The "alg: none" Exploit
Early JWT libraries allowed tokens to specify "alg": "none". Attackers could alter payload values (such as changing "role": "user" to "role": "admin"), delete the signature part, set alg to none, and submit the token to naive backends.
**Prevention:** Hardcode allowable verification algorithms in server-side authorization middleware. Never dynamically accept the algorithm specified in the unverified client header.
Vulnerability 2: Key Confusion Attack (HS256 vs RS256)
If your server expects an asymmetric RSA public key (RS256) but accepts HS256 tokens, an attacker can obtain your public key (often exposed via "/.well-known/jwks.json") and sign a malicious token using the public key as an HMAC secret key!
**Prevention:** Explicitly enforce algorithm checks on your backend verifier:
// Good: Enforce specific expected algorithm
jwt.verify(token, publicKey, { algorithms: ['RS256'] });3. Best Practices for JWT Storage in Web Apps
| Storage Mechanism | XSS Vulnerability | CSRF Vulnerability | Recommendation |
| :--- | :--- | :--- | :--- |
| **LocalStorage / SessionStorage** | High | None | Avoid storing sensitive auth tokens here |
| **HTTP-Only, Secure Cookie** | None | Low (if SameSite set) | **Best Practice** for browser web apps |
Storing JWTs in LocalStorage exposes them to malicious third-party scripts loaded via XSS. Always store authentication session tokens in HttpOnly, Secure, SameSite=Strict cookies.
Inspect JWT Tokens Securely
Pasting proprietary authorization headers into public third-party decoding websites poses severe security risk. Decode and inspect JWT headers, expiration timestamps, and payload attributes client-side using our private [JWT Decoder](/encoding/jwt-decoder).