How to Decode a JWT Token
JWT (JSON Web Token) decoding is a fundamental skill for modern web development. Whether you're debugging authentication issues or inspecting token contents, knowing how to decode JWT tokens is essential. This tutorial covers manual decoding, JavaScript libraries, and security considerations.
Understanding JWT Structure
A JWT consists of three parts separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c| Part | Description | Encoding | |------|-------------|----------| | Header | Algorithm and token type | Base64url | | Payload | Claims and user data | Base64url | | Signature | Verification hash | Base64url |
Method 1: Manual Decoding (No Libraries)
You can decode JWT tokens manually using built-in browser functions:
function decodeJWT(token) {
try {
const [header, payload, signature] = token.split('.'); // Base64url decode
const decodedHeader = JSON.parse(atob(header.replace(/-/g, '+').replace(/_/g, '/')));
const decodedPayload = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
return {
header: decodedHeader,
payload: decodedPayload,
signature: signature
};
} catch (error) {
console.error('Invalid JWT:', error.message);
return null;
}
}
// Usage
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const decoded = decodeJWT(token);
console.log(decoded);
Output:
{
header: { alg: "HS256", typ: "JWT" },
payload: { sub: "1234567890", name: "John Doe", iat: 1516239022 },
signature: "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}Understanding Base64url
JWT uses Base64url encoding (RFC 4648), not standard Base64:
| Standard Base64 | Base64url |
|-----------------|-----------|
| + | - |
| / | _ |
| = (padding) | (omitted) |
That's why we replace - with + and _ with / before decoding.
Method 2: Using jwt-decode Library
For production code, use a dedicated library:
npm install jwt-decodeimport jwtDecode from 'jwt-decode';const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
const decoded = jwtDecode(token);
console.log(decoded);
// { sub: "1234567890", name: "John Doe", iat: 1516239022 }
###jwt-decode Features
- Handles edge cases automatically
- Works in browser and Node.js
- Small bundle size (~5KB)
- No dependencies
Method 3: Using jsonwebtoken (Node.js)
For server-side verification:
npm install jsonwebtokenconst jwt = require('jsonwebtoken');const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
const secret = 'your-secret-key';
// Decode without verification (just reading payload)
const decoded = jwt.decode(token);
// Decode AND verify
try {
const verified = jwt.verify(token, secret);
console.log(verified);
} catch (error) {
console.error('Invalid token:', error.message);
}
Common JWT Claims
When decoding JWTs, you'll encounter these standard claims:
| Claim | Name | Description |
|-------|------|-------------|
| iss | Issuer | Who issued the token |
| sub | Subject | User identifier |
| aud | Audience | Intended recipient |
| exp | Expiration | When token expires (Unix timestamp) |
| iat | Issued At | When token was created |
| nbf | Not Before | Token becomes valid at |
| jti | JWT ID | Unique token identifier |
Custom Claims
{
"sub": "user-123",
"email": "[email protected]",
"role": "admin",
"permissions": ["read", "write", "delete"],
"iat": 1710345600,
"exp": 1710432000
}Checking Token Expiration
A common task is checking if a JWT has expired:
function isTokenExpired(token) {
try {
const decoded = jwtDecode(token);
if (!decoded.exp) return false; // No expiration set const now = Math.floor(Date.now() / 1000);
return decoded.exp < now;
} catch {
return true; // Invalid token = expired
}
}
// Usage
if (isTokenExpired(token)) {
console.log('Token expired, please refresh');
}
Human-Readable Expiration
function formatExpiration(token) {
const decoded = jwtDecode(token);
if (!decoded.exp) return 'No expiration'; const expDate = new Date(decoded.exp 1000);
const now = new Date();
const diff = expDate - now;
if (diff < 0) {
return Expired ${Math.abs(Math.floor(diff / 60000))} minutes ago;
}
const hours = Math.floor(diff / 3600000);
const minutes = Math.floor((diff % 3600000) / 60000);
return Expires in ${hours}h ${minutes}m;
}
Complete JWT Decoder Utility
class JwtDecoder {
static decode(token) {
try {
const [headerB64, payloadB64, signature] = token.split('.'); const header = JSON.parse(
atob(headerB64.replace(/-/g, '+').replace(/_/g, '/'))
);
const payload = JSON.parse(
atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/'))
);
return {
header,
payload,
signature,
isExpired: payload.exp ? payload.exp < Date.now() / 1000 : false,
expiresIn: payload.exp ? payload.exp 1000 - Date.now() : null
};
} catch (error) {
throw new Error(Invalid JWT: ${error.message});
}
}
static getClaim(token, claim) {
const { payload } = this.decode(token);
return payload[claim];
}
static getTimeUntilExpiry(token) {
const { payload } = this.decode(token);
if (!payload.exp) return Infinity;
return payload.exp * 1000 - Date.now();
}
}
// Usage
const decoded = JwtDecoder.decode(token);
console.log(User: ${decoded.payload.sub});
console.log(Expires: ${decoded.expiresIn}ms);
Security Considerations
⚠️ Important: Decoding a JWT is NOT the same as verifying it.
- Decoding just reads the payload (anyone can do this)
- Verifying checks the signature (requires secret key)
// ⚠️ This does NOT verify the token!
const decoded = jwtDecode(token); // Payload could be forged// ✅ This verifies the signature
const verified = jwt.verify(token, secret); // Throws if invalid
Never trust decoded payload data without verification.
Try It Online
Need to quickly inspect a JWT? Use our JWT Decoder for instant decoding with expiration countdown and syntax highlighting.
Conclusion
Decoding JWT tokens is straightforward with jwt-decode library or manual Base64url decoding. Remember to always verify tokens before trusting their contents.
For related tutorials, see How to Encode Base64 in JavaScript which explains the encoding used in JWTs.
---
Related Tools: