If you've worked with modern web authentication, you've encountered a JWT (JSON Web Token) β that long string of characters starting with eyJ... sitting in your browser's localStorage or Authorization header. Decoding a JWT is essential for debugging authentication issues, inspecting user claims, checking expiration, and understanding what your auth server is actually returning. The good news: the first two parts of a JWT are just Base64-encoded JSON β no cryptography required to read them.
Advertisement
Important Security Warning
Never paste production JWTs with sensitive claims into untrusted third-party websites. While decoding only reads the payload (it doesn't need the secret key), the token itself grants access to your system if it's still valid. Our decoder runs entirely in your browser β nothing is sent to any server β making it safe for inspecting real tokens.
What Is a JWT?
A JSON Web Token (JWT, pronounced βjotβ) is a compact, URL-safe way to represent claims between two parties. Defined in RFC 7519, JWTs are used primarily for:
Authentication
After login, the server issues a JWT. The client sends it with each request to prove identity.
Authorization
The JWT payload contains claims like roles and permissions the server trusts without a DB lookup.
Information Exchange
Microservices pass signed JWTs to share verified user data without re-querying the auth service.
JWT Structure: 3 Parts Separated by Dots
A JWT always has the format xxxxx.yyyyy.zzzzz β three Base64URL-encoded segments separated by periods:
Example JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
{
"alg": "HS256", // Signing algorithm: HS256, RS256, ES256...
"typ": "JWT" // Token type (always "JWT")
}The header is Base64URL-encoded but not encrypted. alg is critical β it tells the receiver which algorithm was used to sign the token. Never set alg to βnoneβ β this disables signature verification and is a severe security vulnerability.
{
"sub": "1234567890", // Subject (user ID)
"name": "John Doe", // Custom claim
"email": "john@example.com",
"roles": ["admin", "user"],
"iat": 1516239022, // Issued At (Unix timestamp)
"exp": 1516325422, // Expiration (Unix timestamp)
"nbf": 1516239022 // Not Before (optional)
}The payload contains claims β statements about the user plus metadata. Standard claims include sub (subject), iat (issued at), exp (expiration), and iss (issuer). Custom claims like roles and email are added by your auth server. The payload is Base64-encoded, not encrypted β anyone with the token can read it.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key // Only the server knows this
)The signature verifies that the token hasn't been tampered with. It requires the secret key (or private key for RSA/ECDSA). You cannot forge a valid JWT without the key. Decoding the payload does NOT require the signature β but trusting the payload absolutely requires verifying it on the server.
β¨ Decode Any JWT Instantly β 100% Client-Side
Paste your JWT and instantly see the decoded header, payload, and expiration details. Token never leaves your browser β no server calls, no logging.
Open JWT Decoder βAdvertisement
How to Decode a JWT (3 Methods)
Method 1: Online Decoder (No Code)
- Copy your JWT token (from your browser DevTools, Postman, or auth logs)
- Open the JWT Decoder
- Paste the token into the input field
- The header and payload are immediately shown as formatted JSON
- Check expiration: the tool converts
expUnix timestamp to a human-readable date
Method 2: JavaScript (Browser / Node.js)
// Decode JWT payload without verifying signature
// ONLY use this for debugging β never for auth decisions
function decodeJwt(token) {
const parts = token.split('.');
if (parts.length !== 3) throw new Error('Invalid JWT format');
// Base64URL β Base64 β JSON
const decode = (str) => {
const base64 = str.replace(/-/g, '+').replace(/_/g, '/');
const json = atob(base64.padEnd(base64.length + (4 - base64.length % 4) % 4, '='));
return JSON.parse(json);
};
return {
header: decode(parts[0]),
payload: decode(parts[1]),
// Signature is binary β not decoded here
};
}
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U";
const { header, payload } = decodeJwt(token);
console.log(header); // { alg: 'HS256', typ: 'JWT' }
console.log(payload); // { sub: '1234567890' }
// Check if expired
if (payload.exp && Date.now() / 1000 > payload.exp) {
console.log('Token expired at:', new Date(payload.exp * 1000));
}Method 3: Python
import base64, json
from datetime import datetime
def decode_jwt(token: str) -> dict:
"""Decode JWT payload without signature verification (debug only)."""
parts = token.split('.')
if len(parts) != 3:
raise ValueError("Invalid JWT format")
def decode_part(part):
# Add padding, convert Base64URL to Base64
padding = 4 - len(part) % 4
padded = part + '=' * (padding % 4)
padded = padded.replace('-', '+').replace('_', '/')
return json.loads(base64.b64decode(padded))
header = decode_part(parts[0])
payload = decode_part(parts[1])
# Convert timestamps to human-readable
if 'exp' in payload:
payload['_exp_human'] = datetime.fromtimestamp(payload['exp']).isoformat()
if 'iat' in payload:
payload['_iat_human'] = datetime.fromtimestamp(payload['iat']).isoformat()
return {'header': header, 'payload': payload}
# Or use the pyjwt library for full verification:
# pip install pyjwt
# import jwt
# payload = jwt.decode(token, secret, algorithms=["HS256"])Standard JWT Claims Reference
| Claim | Name | Description | Format |
|---|---|---|---|
| sub | Subject | Unique identifier of the user (user ID) | String |
| iss | Issuer | Who created the token (e.g., "auth.example.com") | String / URI |
| aud | Audience | Who the token is intended for (API or app) | String or Array |
| exp | Expiration | Token expires at this time β reject after | Unix timestamp |
| iat | Issued At | When the token was created | Unix timestamp |
| nbf | Not Before | Token is invalid before this time | Unix timestamp |
| jti | JWT ID | Unique ID to prevent token replay attacks | String (UUID) |
| name | Name | User's full name (custom/OIDC claim) | String |
| User's email address (custom/OIDC claim) | String | ||
| roles | Roles | User permissions/roles (custom claim) | Array of strings |
JWT Security: What Developers Get Wrong
localStorage is accessible to any JavaScript on the page β including XSS-injected scripts. Prefer HttpOnly cookies for storage, which JavaScript cannot access.
A decoded JWT is not the same as a verified JWT. Always check exp on the server before trusting claims.
The alg=none attack: an attacker removes the signature and sets alg to "none". Always hardcode the expected algorithm server-side; never read it from the token.
The payload is Base64-encoded, not encrypted. Anyone with the token can read it. Never store passwords, credit cards, or PII in JWT claims.
Frequently Asked Questions
Can I verify the JWT signature online?
For HS256 (shared secret) tokens, you can verify if you know the secret key. Our decoder focuses on decoding the payload (which requires no key) rather than verification, since the secret should never be shared with client-side tools. For RS256/ES256 (public/private key), signature verification requires only the public key.
What is the difference between a JWT and a session token?
A session token is an opaque random string; the server stores session data and looks it up on each request (stateful). A JWT contains the data itself, signed by the server β the receiver can validate and read claims without a DB lookup (stateless). JWTs are better for microservices and APIs; sessions are simpler for traditional web apps.
Why does my JWT start with eyJ?
All JWTs start with eyJ because the header always starts with {"alg", and {"a in Base64URL encoding is eyJh. It's a predictable pattern, not a security issue β the header contents are intentionally readable.