How to Decode JWT Tokens: A Developer's Guide
JSON Web Tokens (JWTs) are the backbone of modern web authentication. If you've ever worked with APIs, OAuth, or single sign-on, you've encountered JWTs. Despite their ubiquity, many developers treat them as opaque strings. This guide demystifies JWTs — showing you their structure, how to decode them, and critical security considerations.
What Is a JWT?
A JWT (pronounced "jot") is a compact, URL-safe token that represents claims between two parties. It's typically used to authenticate API requests. After a user logs in, the server issues a JWT that the client includes in subsequent requests (usually in the Authorization header as a Bearer token).
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT Structure
A JWT consists of three parts separated by dots: header.payload.signature. Each part is Base64URL-encoded JSON.
1. Header
Specifies the token type and signing algorithm:
{
"alg": "HS256",
"typ": "JWT"
}
Common algorithms: HS256 (HMAC-SHA256), RS256 (RSA-SHA256), ES256 (ECDSA-SHA256).
2. Payload (Claims)
Contains the actual data — user identity, permissions, and metadata:
{
"sub": "1234567890",
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
Standard claims include:
sub(subject): user identifieriat(issued at): when the token was createdexp(expiration): when the token expiresiss(issuer): who created the tokenaud(audience): intended recipient
3. Signature
Verifies the token hasn't been tampered with:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
How to Decode a JWT
In JavaScript
// Decode payload (no verification)
function decodeJWT(token) {
const parts = token.split('.');
const header = JSON.parse(atob(parts[0]));
const payload = JSON.parse(atob(parts[1]));
return { header, payload };
}
// Check expiration
function isExpired(token) {
const { payload } = decodeJWT(token);
return payload.exp && Date.now() >= payload.exp * 1000;
}
In Python
import jwt # pip install PyJWT
# Decode without verification (for inspection)
decoded = jwt.decode(token, options={"verify_signature": False})
# Decode with verification
decoded = jwt.decode(token, secret_key, algorithms=["HS256"])
Command Line
# Decode payload echo "eyJzdWIiOi..." | cut -d. -f2 | base64 -d | jq .
JWT vs Session-Based Auth
- JWTs are stateless: The server doesn't need to store session data. The token contains everything needed for authentication.
- Sessions require server storage: Each session needs a database record or Redis entry.
- JWTs work across services: Ideal for microservices and distributed systems.
- Sessions are easier to revoke: Delete the session record. JWTs remain valid until expiration.
Security Best Practices
- Always verify the signature: Decoding is not verification. Always validate the signature server-side before trusting claims.
- Set short expiration times: 15-60 minutes for access tokens. Use refresh tokens for longer sessions.
- Use strong secrets: For HMAC algorithms, use a secret of at least 256 bits. Consider using asymmetric algorithms (RS256) for better security.
- Don't store sensitive data: The payload is Base64-encoded, NOT encrypted. Anyone can read it. Never include passwords, credit card numbers, or secrets.
- Use HTTPS only: JWTs sent over HTTP can be intercepted.
- Validate the
algheader: The "none" algorithm attack tricks servers into accepting unsigned tokens. Always whitelist accepted algorithms.
Common JWT Attacks
Algorithm Confusion
An attacker changes the algorithm from RS256 to HS256 and signs with the public key (which is public!). The server, using the same key for verification, accepts the forged token. Mitigation: always enforce the expected algorithm.
Token Theft
If an attacker obtains a valid JWT, they can impersonate the user until it expires. Mitigations: short expiration times, token binding, and storing tokens securely (httpOnly cookies, not localStorage).
None Algorithm
Some JWT libraries accept "alg": "none", allowing unsigned tokens. Always reject tokens with no algorithm.
Where to Store JWTs
- httpOnly cookies: Best for web apps — immune to XSS attacks
- Memory (JavaScript variable): Secure but lost on page refresh
- localStorage: Persistent but vulnerable to XSS — avoid if possible
Conclusion
JWTs are powerful, flexible, and ubiquitous in modern web development. Understanding their structure helps you debug authentication issues, inspect API tokens, and build more secure applications. Always verify signatures server-side, use short expiration times, and never trust client-side token data without validation.