How to Decode and Encode JWT Tokens (Without Sending Them to a Server)
JSON Web Tokens (JWTs) are everywhere — they are the authentication token your API issues after login, the session cookie your frontend stores, and the Bearer token your app sends with every request. Despite their ubiquity, most developers have never looked inside one. This guide explains exactly what a JWT contains, how to decode and encode them safely, and what security pitfalls to avoid.
What Is a JWT?
A JWT is a compact, URL-safe string made of three Base64URL-encoded parts separated by dots: header.payload.signature. It is defined in RFC 7519 and used to transmit signed (or encrypted) claims between parties.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJBbGljZSIsImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxNzAwMDg2NDAwfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cThe Three Parts
1. Header
The header is a JSON object describing the token type and the signing algorithm used. Base64URL-decode the first segment to read it.
{
"alg": "HS256",
"typ": "JWT"
}Common algorithm values: HS256 (HMAC-SHA256, symmetric — same secret to sign and verify), RS256 (RSA-SHA256, asymmetric — private key to sign, public key to verify), ES256 (ECDSA-SHA256, smaller signatures than RSA).
2. Payload (Claims)
The payload contains the actual data — called claims. There are registered claims (standard fields from the RFC), public claims, and private claims.
{
"sub": "user_123",
"name": "Alice",
"email": "[email protected]",
"roles": ["admin", "editor"],
"iat": 1700000000,
"exp": 1700086400
}| Claim | Name | Meaning |
|---|---|---|
| sub | Subject | The user or entity the token refers to (usually a user ID) |
| iss | Issuer | Who issued the token (e.g. "auth.myapp.com") |
| aud | Audience | Who the token is intended for (e.g. "api.myapp.com") |
| exp | Expiration | Unix timestamp after which the token is invalid |
| iat | Issued At | Unix timestamp of when the token was issued |
| nbf | Not Before | Token is invalid before this Unix timestamp |
| jti | JWT ID | Unique identifier for the token (for revocation) |
3. Signature
The signature proves the token was not tampered with. For HS256, it is computed as: HMAC-SHA256(base64url(header) + '.' + base64url(payload), secret). Only the party that knows the secret (or holds the private key for RS256/ES256) can produce a valid signature.
Critical: the payload is NOT encrypted
Base64URL encoding is not encryption — anyone can decode the header and payload without any key. Never put sensitive data (passwords, credit card numbers, internal IPs) in a JWT payload. Only put data you are comfortable being visible to the token holder.
How to Decode a JWT
In the browser (without a library)
function decodeJwt(token) {
const [header, payload, signature] = token.split('.');
const decode = (part) => JSON.parse(atob(part.replace(/-/g, '+').replace(/_/g, '/')));
return {
header: decode(header),
payload: decode(payload),
signature // raw base64url string — cannot be decoded meaningfully
};
}
const { header, payload } = decodeJwt('eyJhbGci...');
console.log(payload.sub); // "user_123"
console.log(new Date(payload.exp * 1000)); // expiry as DateUse our JWT Decoder
Paste any JWT into our JWT Decoder to instantly see the header, payload, and expiry time. Everything is decoded client-side — your token never leaves the browser.
In Node.js
function decodeJwt(token) {
const [header, payload] = token.split('.');
const decode = (part) =>
JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
return { header: decode(header), payload: decode(payload) };
}
// For VERIFICATION (signature checking), use a library:
// npm install jsonwebtoken
const jwt = require('jsonwebtoken');
const decoded = jwt.verify(token, process.env.JWT_SECRET);How to Encode (Sign) a JWT
Signing a JWT requires a secret or private key. This should only happen on your server — never in frontend code that users can inspect.
const jwt = require('jsonwebtoken');
const payload = {
sub: 'user_123',
name: 'Alice',
roles: ['admin'],
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour
};
const token = jwt.sign(payload, process.env.JWT_SECRET, { algorithm: 'HS256' });
// → "eyJhbGci..."import jwt
import time
payload = {
"sub": "user_123",
"name": "Alice",
"iat": int(time.time()),
"exp": int(time.time()) + 3600 # 1 hour
}
token = jwt.encode(payload, "your-secret-key", algorithm="HS256")
decoded = jwt.decode(token, "your-secret-key", algorithms=["HS256"])Test token structure with our JWT Encoder
Use our JWT Encoder to construct and preview JWT payloads before wiring up your backend. Great for prototyping auth flows without spinning up a server.
Common JWT Security Mistakes
- Accepting "alg: none": Some early libraries would accept tokens with no signature if the algorithm was set to
"none". Always explicitly allowlist the algorithms you accept. - Not checking expiry on the server: Decoding a JWT is not the same as validating it. Always call
jwt.verify()(not justjwt.decode()) on the server side — this checks the signature AND theexpclaim. - Storing JWTs in localStorage: localStorage is accessible to any JavaScript on the page, making it vulnerable to XSS attacks. Prefer
httpOnlycookies for storing JWTs in web apps. - Using a weak secret: For HS256, use a cryptographically random secret of at least 256 bits (32 bytes).
openssl rand -base64 32generates a good one. - Putting sensitive data in the payload: Anyone who holds the token can decode the payload. Keep it to identity claims only.
Inspect any JWT instantly
Paste your JWT to decode the header and payload, check expiry, and verify the structure — all in your browser, no server upload.