What a JWT looks like
A JSON Web Token is three Base64URL-encoded strings separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cThe three parts are: header . payload . signature
Decode any JWT instantly with the free JWT decoder — paste the token and see the decoded header and payload in readable JSON format.
Part 1: The header
The header contains metadata about the token — specifically which algorithm was used to sign it. Decoding the first part of the example above:
{
"alg": "HS256",
"typ": "JWT"
}Common alg values:
HS256— HMAC-SHA256 (symmetric — same secret signs and verifies)RS256— RSA-SHA256 (asymmetric — private key signs, public key verifies)ES256— ECDSA-SHA256 (asymmetric, more compact than RSA)none— No signature. Never accept this in production — it's a known attack vector where an attacker removes the signature and sets alg to "none" to forge tokens
Part 2: The payload (claims)
The payload contains the actual data — user ID, roles, expiration time, etc. Decoding the second part:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}Standard registered claims (defined in RFC 7519):
| Claim | Name | Meaning |
|---|---|---|
| sub | Subject | Who the token is about (usually user ID) |
| iss | Issuer | Who created the token (your auth server) |
| aud | Audience | Who the token is intended for (your API) |
| exp | Expiration | Unix timestamp when the token expires |
| iat | Issued At | Unix timestamp when the token was created |
| nbf | Not Before | Token is invalid before this Unix timestamp |
| jti | JWT ID | Unique identifier for this token (prevents replay) |
The exp claim is the most critical for security. If your server doesn't validate expiration, tokens that should be expired remain valid indefinitely. The JWT decoder shows the expiration as a human-readable date so you can quickly check whether a token has expired.
Part 3: The signature
The signature is created by combining the encoded header and payload, then signing them with the secret key:
HMACSHA256(
base64url(header) + "." + base64url(payload),
secret
)The signature is what makes JWTs trustworthy. Anyone can read the header and payload — they're just Base64-encoded, not encrypted. But only the server that knows the secret key can create a valid signature. If an attacker modifies the payload (changing the user ID or role), the signature no longer matches, and any server that verifies the signature will reject the token.
Critical point: The payload is readable by anyone. Never put sensitive data in a JWT payload — no passwords, no credit card numbers, no SSNs. JWTs provide integrity (tamper detection) but not confidentiality (secrecy).
How to decode a JWT manually
In a browser console:
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U";
const [header, payload] = token.split('.');
console.log(JSON.parse(atob(header))); // Header object
console.log(JSON.parse(atob(payload))); // Payload objectNote: atob() only handles standard Base64. Base64URL uses - instead of + and _ instead of /. For tokens with these characters in the payload, you need to replace them before decoding:
const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
JSON.parse(atob(base64));Where JWTs appear in API requests
- Authorization header (most common):
Authorization: Bearer eyJ... - Cookie:
Cookie: token=eyJ...— common for browser-based apps (HTTP-only cookies prevent JavaScript access) - Query parameter (avoid):
?token=eyJ...— tokens in URLs appear in server logs and browser history
Related tools
- Free JWT Decoder — decode and inspect JWT tokens in your browser
- Free Base64 Encoder/Decoder — decode the individual parts of a JWT manually
- Free Unix Timestamp Converter — convert the exp and iat timestamps to readable dates
Written by Achraf A., founder of TheFreeAITools.