Introduction
JWT tokens look like random gibberish, but they're actually just Base64-encoded JSON. You can decode any JWT without a secret key - the payload is readable by design. This guide shows you how to decode JWTs manually using browser DevTools, command-line tools, and our online debugger.
Understanding the Three Parts
A JWT has three parts separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c │ │ │ └── Header (Base64) └── Payload (Base64) └── Signature
The header and payload are just Base64URL-encoded JSON. You can decode them anywhere. The signature cannot be decoded - it's a cryptographic hash, not encoded data.
Method 1: Browser DevTools
The fastest way - no tools needed, works in any browser:
// Open browser console (F12), paste:
// Split the token
const token = 'your.jwt.token';
const [header, payload] = token.split('.');
// Decode each part
const headerData = JSON.parse(atob(header));
const payloadData = JSON.parse(atob(payload));
console.log('Header:', headerData);
console.log('Payload:', payloadData);
// Check expiration
if (payloadData.exp) {
const expires = new Date(payloadData.exp * 1000);
const isExpired = Date.now() > payloadData.exp * 1000;
console.log('Expires:', expires.toLocaleString());
console.log('Expired:', isExpired);
}Note: atob() works for standard Base64. JWTs use Base64URL which replaces+ and /. For production use, replace them first:atob(payload.replace(/-/g, '+').replace(/_/g, '/'))
Method 2: Command Line
# Using bash (macOS/Linux)
echo 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0' | base64 -d
# Using PowerShell
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0'))
# Using Node.js
node -e "console.log(JSON.parse(Buffer.from('eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0', 'base64').toString()))"Method 3: Online Debugger
For quick inspection without writing code, use ourJWT Debugger. Paste any JWT and instantly see:
- Decoded header (algorithm, token type)
- Decoded payload (all claims with human-readable timestamps)
- Expiration status (expired or valid)
- Signature verification (if you provide the secret)
What to Look For When Decoding
- Expiration (exp) - Is the token still valid? Compare
expto current Unix timestamp. - Algorithm (alg) - If it says
none, the token has no signature protection. Reject it. - Issuer (iss) - Does it match your auth server? Prevents token confusion across services.
- Audience (aud) - Is this token meant for your service?
- Sensitive data - Are there passwords, API keys, or PII in the payload? (There shouldn't be!)
Quick Reference: Common Claims
| Claim | Meaning | Example Value |
|---|---|---|
| sub | Subject (user ID) | "1234567890" |
| iat | Issued at | 1516239022 |
| exp | Expiration time | 1516242622 |
| iss | Issuer | "auth.example.com" |
| aud | Audience | "api.example.com" |
Key Takeaways
- JWT payload is Base64-encoded, not encrypted - anyone can read it
- Use
atob()in browser orbase64 -din terminal to decode - Always check
exp,iss, andaudclaims when debugging - Decoding is not verification - you need the secret key to verify the signature
- Use our JWT Debugger for quick, visual token inspection