JWT (JSON Web Token) is the most widely used token format for authentication on the web. If you’ve ever worked with OAuth, API keys, or session tokens, you’ve almost certainly encountered a JWT. Here’s how to decode one, understand what’s inside, and avoid the security pitfalls that catch even experienced developers.
What Is a JWT?
A JWT is a compact, URL-safe string that carries a JSON payload. It looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNjE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
It’s three Base64URL-encoded segments separated by dots:
HEADER.PAYLOAD.SIGNATURE
Each part has a specific purpose:
| Part | Contains | Purpose |
|---|---|---|
| Header | Algorithm + token type | Tells the server how to verify the signature |
| Payload | Claims (user data, expiration, etc.) | Carries the actual information |
| Signature | Cryptographic hash | Proves the token hasn’t been tampered with |
How to Decode a JWT (Step by Step)
Method 1: Use the Online Decoder (Fastest)
- Open our JWT Decoder
- Paste your JWT into the input field
- Instantly see the decoded header, payload, and signature status
- Check expiration time, issuer, and all claims at a glance
The decoder runs entirely in your browser — your token is never sent to any server.
Method 2: Decode Manually (No Tools Needed)
Since a JWT is just Base64URL-encoded JSON, you can decode it with any Base64 decoder or even from the command line.
Step 1: Split the token by dots
Part 1 (Header): eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Part 2 (Payload): eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNjE2MjM5MDIyfQ
Part 3 (Signature): SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Step 2: Base64URL-decode each part
Base64URL is slightly different from standard Base64:
-replaces+_replaces/- No
=padding
You can use our Base64 Encoder/Decoder or the command line:
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' | base64 -d
Step 3: Read the JSON
Decoded header:
{
"alg": "HS256",
"typ": "JWT"
}
Decoded payload:
{
"sub": "1234567890",
"name": "Alice",
"iat": 1616239022
}
The signature is a binary hash — you can’t “read” it, but you can verify it with the correct secret key.
Method 3: Decode in JavaScript (One Line)
JSON.parse(atob(token.split('.')[1]))
This decodes the payload directly. Note: atob() handles standard Base64 but may fail on Base64URL characters. For production code, add padding and character replacement:
function decodeJWT(token) {
const payload = token.split('.')[1];
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(atob(base64));
}
Method 4: Decode in Python
import base64
import json
token = "eyJhbGciOiJIUzI1NiIs..."
payload = token.split('.')[1]
# Add padding
payload += '=' * (4 - len(payload) % 4)
decoded = json.loads(base64.urlsafe_b64decode(payload))
print(decoded)
Understanding the JWT Header
The header is typically small and contains two fields:
{
"alg": "HS256",
"typ": "JWT"
}
Common Algorithms
| Algorithm | Type | Key | Security Level |
|---|---|---|---|
| HS256 | Symmetric | Shared secret | Good for internal services |
| HS384 | Symmetric | Shared secret | Stronger variant of HS256 |
| HS512 | Symmetric | Shared secret | Strongest symmetric option |
| RS256 | Asymmetric | RSA public/private key pair | Standard for public APIs |
| RS512 | Asymmetric | RSA public/private key pair | Higher security RSA |
| ES256 | Asymmetric | ECDSA key pair | Compact, fast, modern |
| EdDSA | Asymmetric | Ed25519 key pair | Cutting-edge, fastest |
| none | None | No signature | NEVER use in production |
Key takeaway: If you see "alg": "none" in a JWT, it means the token is unsigned and can be forged by anyone. This is a critical security vulnerability.
Understanding the JWT Payload
The payload contains claims — statements about the user and the token itself. There are three types:
Registered Claims (Standard)
These are predefined by the JWT specification (RFC 7519):
| Claim | Full Name | Description | Example |
|---|---|---|---|
sub |
Subject | Who the token is about (usually user ID) | "1234567890" |
iss |
Issuer | Who created the token | "auth.example.com" |
aud |
Audience | Who the token is for | "api.example.com" |
exp |
Expiration | When the token expires (Unix timestamp) | 1716239022 |
iat |
Issued At | When the token was created | 1616239022 |
nbf |
Not Before | Token is not valid before this time | 1616239022 |
jti |
JWT ID | Unique identifier for the token | "a1b2c3d4" |
Public Claims
Custom claims registered with IANA or using collision-resistant names:
{
"email": "[email protected]",
"name": "Alice",
"role": "admin"
}
Private Claims
Custom claims agreed upon between parties:
{
"team_id": "engineering",
"permissions": ["read", "write", "deploy"]
}
Understanding the JWT Signature
The signature is created by combining the encoded header and payload with a secret:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Important: You can always decode a JWT without the secret. But you can only verify it with the secret (or public key for asymmetric algorithms). Decoding tells you what’s inside. Verification tells you whether to trust it.
How to Check if a JWT Is Expired
The exp claim is a Unix timestamp. To check expiration:
In JavaScript:
const payload = JSON.parse(atob(token.split('.')[1]));
const isExpired = payload.exp * 1000 < Date.now();
console.log(isExpired ? 'Token expired' : 'Token valid');
Using the online decoder: Our JWT Decoder automatically converts timestamps to human-readable dates and flags expired tokens.
Quick manual check:
Paste the exp value into any Unix timestamp converter. If the date is in the past, the token is expired.
JWT Security Best Practices
Do Not Store Sensitive Data in the Payload
JWTs are encoded, not encrypted. Anyone can decode the payload. Never put passwords, credit card numbers, or secret keys in a JWT.
Always Verify the Signature
Decoding is not the same as verifying. Your server must verify the signature before trusting any claims. Use a proper JWT library for your language — don’t roll your own verification.
Set Short Expiration Times
| Use Case | Recommended exp |
|---|---|
| Access tokens (API calls) | 15 minutes to 1 hour |
| Refresh tokens | 7-30 days |
| Email verification links | 24 hours |
| Password reset tokens | 15-60 minutes |
Validate the alg Header
Your server should whitelist allowed algorithms. A classic attack (CVE-2015-2951) tricks servers into accepting "alg": "none", bypassing signature verification entirely. Always enforce the expected algorithm.
Use HTTPS Only
JWTs sent over HTTP can be intercepted. Always transmit tokens over HTTPS and set the Secure flag on cookies.
Implement Token Revocation
JWTs are stateless — there’s no built-in way to revoke them. For sensitive systems, maintain a blocklist of revoked token IDs (jti) or use short-lived tokens with refresh rotation.
JWT vs Session Cookies vs API Keys
| Feature | JWT | Session Cookie | API Key |
|---|---|---|---|
| Stateless | Yes | No (server stores session) | Depends |
| Scalable | Excellent (no server storage) | Harder (shared session store) | Excellent |
| Revocable | Difficult (needs blocklist) | Easy (delete session) | Easy (delete key) |
| Cross-domain | Yes (sent in headers) | Limited (same-site) | Yes |
| Payload | Rich (carries claims) | None (just an ID) | None |
| Expiration | Built-in (exp claim) |
Server-controlled | Usually none |
| Best for | SPAs, mobile apps, microservices | Traditional web apps | Server-to-server |
Debugging JWT Issues
When authentication fails, decode the JWT first to check:
- Is it expired? Check the
expclaim against the current time - Is the audience correct? The
audclaim must match your API’s expected value - Is the issuer correct? The
issclaim must match your auth server - Is the algorithm expected? A mismatch means verification will fail
- Is the token malformed? It should have exactly three dot-separated parts
Our JWT Decoder highlights all of these issues automatically, making it the fastest way to debug token problems.
Frequently Asked Questions
Is it safe to decode a JWT in the browser?
Yes. Decoding a JWT only reveals the payload — it doesn’t compromise security because the signature can’t be forged without the secret key. Our JWT Decoder runs entirely client-side, so your tokens never leave your browser.
Can I modify a JWT and reuse it?
No. If you change any part of the header or payload, the signature becomes invalid. The server will reject the modified token (assuming proper signature verification).
What’s the difference between decoding and verifying a JWT?
Decoding extracts the header and payload — anyone can do this. Verifying checks the signature against the secret key to confirm the token hasn’t been tampered with. Always verify on the server side.
Why is my JWT so long?
JWT size grows with the number of claims. Every claim adds to the payload, which adds to the Base64 string. Keep payloads lean — store only essential claims and look up additional data server-side.
Can JWTs be encrypted?
Yes. JWE (JSON Web Encryption) encrypts the payload so it can’t be read without a decryption key. Standard JWTs (JWS) are only signed, not encrypted — the payload is visible to anyone who decodes it.
How do I decode a JWT from an HTTP header?
Authorization headers typically use the format Bearer <token>. Strip the “Bearer " prefix, then decode the remaining string. In code: const token = authHeader.replace('Bearer ', '').
Related Tools
- JWT Decoder — Decode and inspect JWT tokens instantly in your browser
- Base64 Encoder/Decoder — Encode and decode Base64 strings manually
- Hash Generator — Generate SHA-256, MD5, and other hashes
- JSON Formatter — Format the decoded JWT payload for readability
- Password Generator — Generate strong secrets for JWT signing