JWT Payload Decoder
Decode the JWT payload to view all claims and data stored in the token. The payload contains the actual information the token conveys.
JWT Payload Structure
The payload is the second part of the JWT (between the dots) and contains claims:
``json
{
"sub": "1234567890",
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
`
Claim Types
| Category | Claims | Description |
|---|---|---|
| Registered | iss, sub, aud, exp, nbf, iat, jti | Standard claims with defined meanings |
| Public | name, email, picture | Commonly used claims (IANA registry) |
| Private | role, permissions, tenant_id | Application-specific claims |
Payload Decoder Implementation
`javascript
function decodeJWTPayload(token) {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWT format');
}
const payloadPart = parts[1];
// Base64URL decode
const base64 = payloadPart.replace(/-/g, '+').replace(/_/g, '/');
const decoded = atob(base64);
const payload = JSON.parse(decoded);
// Analyze claims
const analysis = {
raw: payload,
registeredClaims: {},
customClaims: {},
timestamps: {}
};
const registered = ['iss', 'sub', 'aud', 'exp', 'nbf', 'iat', 'jti'];
for (const [key, value] of Object.entries(payload)) {
if (registered.includes(key)) {
analysis.registeredClaims[key] = value;
// Convert timestamps to readable dates
if (['exp', 'nbf', 'iat'].includes(key)) {
analysis.timestamps[key] = new Date(value * 1000).toISOString();
}
} else {
analysis.customClaims[key] = value;
}
}
return analysis;
}
`
Registered Claims Reference
| Claim | Name | Purpose |
|---|---|---|
| iss | Issuer | Identifies token creator |
| sub | Subject | Identifies the user/entity |
| aud | Audience | Intended recipients |
| exp | Expiration | When token becomes invalid |
| nbf | Not Before | When token becomes valid |
| iat | Issued At | When token was created |
| jti | JWT ID | Unique identifier for token |
Best Practices
Keep payload small (affects token size)- Never store sensitive data (passwords, secrets)
- Use registered claims when appropriate
- Include only necessary information
The Attacks a Verifier Must Stop
alg: none. A token declaring no algorithm with an empty signature. A library that
trusts the header's alg will accept it as valid. Always specify the expected algorithm
when verifying rather than reading it from the token.
Algorithm confusion. A token signed with HMAC using the server's *public* RSA key as the
secret. If the verifier picks the algorithm from the header, an RS256 verifier can be tricked
into running HS256 with a key the attacker already has.
Weak secrets. HS256 with a short or dictionary secret is brute-forceable offline from a
single captured token. Use at least 256 bits of real entropy.
No expiry check. exp is a claim, not an enforcement. A library that decodes without
verifying, or code that reads claims from a decoded-but-unverified token, accepts expired and
forged tokens alike.
`javascript
// Specify the algorithm; never trust the header's
jwt.verify(token, secret, { algorithms: ['HS256'] });
`
Anyone Can Read the Payload
A JWT is signed, not encrypted. The payload is base64url — readable by anyone holding the
token, including the browser it is stored in. Never put anything in it you would not print
on a postcard: no passwords, no PII beyond an identifier, no internal keys.
Revocation Is the Hard Part
A signed token is valid until it expires, and there is no way to withdraw one. Options:
| Approach | Cost |
|---|---|
| Short expiry (5–15 min) plus refresh tokens | Standard; adds a refresh endpoint |
| A denylist of revoked JTIs | Reintroduces the state JWTs were meant to avoid |
| Rotate the signing key | Revokes every token at once |
| Version claim checked against the user record | A database read per request |
If you need immediate revocation for every session, a server-side session is a simpler and
more honest choice than a JWT.Where to Store One
| Location | XSS-safe | CSRF-safe |
|---|---|---|
localStorage | No | Yes |
| sessionStorage` | No | Yes |
| Cookie (HttpOnly, Secure, SameSite) | Yes | Yes, with SameSite |
| In-memory | Yes | Yes |
An HttpOnly cookie is the safest default. Any token reachable from JavaScript is reachable by
any script that gets injected.