How to Decode a JWT Token Safely (JavaScript, Python, and Online)
Every developer working with modern APIs eventually stares at a long string starting with eyJ and wonders what is inside it. That string is a JSON Web Token (JWT), and decoding it is easy — it is just base64url-encoded JSON. But there are two things many tutorials skip: decoding a token is not the same as verifying it, and a JWT is a live credential that should never be pasted into a website that ships it to a server. This guide covers how to decode a JWT in JavaScript and Python, and how to do it safely.
A JWT Is Three Base64url Parts Joined by Dots
A JWT looks like xxxxx.yyyyy.zzzzz — three segments separated by dots:
- Header — JSON describing the signing algorithm, e.g. {"alg":"HS256","typ":"JWT"}
- Payload — JSON claims: who the user is, when the token expires, custom data
- Signature — raw bytes produced by signing the first two parts with a secret or private key
The header and payload are encoded with base64url, a URL-safe variant of base64. It replaces + with -, replaces / with _, and drops the trailing = padding. That is exactly why a naive atob() call sometimes throws an error on a valid token — you have to convert base64url back to standard base64 first.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 <- header (base64url JSON) .eyJzdWIiOiIxMDAxIiwibmFtZSI6IlJhdmkiLCJleHAiOjE3ODM0MjU2MDB9 <- payload .k5Jf3mZQx9VbW1cAqT0dO4nHc2xLmR8sYp6uE_wG1jU <- signature (binary, not JSON)
Decoding a JWT in JavaScript (No Library Needed)
In the browser or Node.js 16+, you can decode a JWT with a few lines. The key steps are: split on dots, fix the base64url characters, add padding back, then decode and parse.
function decodeJwtPart(part) {
// 1. base64url -> base64
let b64 = part.replace(/-/g, '+').replace(/_/g, '/');
// 2. restore padding (length must be a multiple of 4)
while (b64.length % 4 !== 0) b64 += '=';
// 3. decode and handle UTF-8 correctly
const json = decodeURIComponent(
atob(b64)
.split('')
.map((c) => '%' + c.charCodeAt(0).toString(16).padStart(2, '0'))
.join('')
);
return JSON.parse(json);
}
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDAxIiwibmFtZSI6IlJhdmkifQ.sig';
const [headerB64, payloadB64] = token.split('.');
console.log(decodeJwtPart(headerB64)); // { alg: 'HS256', typ: 'JWT' }
console.log(decodeJwtPart(payloadB64)); // { sub: '1001', name: 'Ravi' }In Node.js you can skip atob() entirely, because Buffer understands base64url natively:
const payload = JSON.parse(
Buffer.from(token.split('.')[1], 'base64url').toString('utf8')
);
console.log(payload);Decoding a JWT in Python
Python's standard library handles this cleanly with base64.urlsafe_b64decode. The only trick is restoring the stripped padding:
import base64
import json
def decode_jwt_part(part: str) -> dict:
padded = part + '=' * (-len(part) % 4) # restore padding
raw = base64.urlsafe_b64decode(padded)
return json.loads(raw)
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDAxIn0.sig"
header_b64, payload_b64, signature_b64 = token.split('.')
print(decode_jwt_part(header_b64)) # {'alg': 'HS256', 'typ': 'JWT'}
print(decode_jwt_part(payload_b64)) # {'sub': '1001'}If you already use the PyJWT library, you can decode without verification explicitly — note how the API forces you to acknowledge that signature checking is off:
import jwt # pip install PyJWT
claims = jwt.decode(token, options={"verify_signature": False})
print(claims) # payload dict — UNVERIFIED, for inspection onlyDecoding Is Not Verifying — Never Trust a Decoded Token
This is the single most important thing to understand. Base64url is an encoding, not encryption and not a signature check. Anyone can decode a JWT, and anyone can construct one with any claims they like. What makes a JWT trustworthy is the signature, and checking the signature requires the secret key (HS256) or the public key (RS256).
- Decoding — reading the JSON inside. Safe for debugging, useless for security decisions.
- Verifying — recomputing the signature and comparing it, plus checking expiry, issuer, and audience. This is what your server must do on every request.
A classic vulnerability is a backend that calls jwt.decode() (or reads the payload manually) and trusts the role or userId claim without verification. An attacker just edits the payload, re-encodes it, and becomes an admin. Always use jwt.verify() in Node.js or jwt.decode(token, key, algorithms=[...]) in PyJWT on the server side.
Why You Should Not Paste Production Tokens into Random Websites
A JWT is a bearer token: whoever holds it can use it. If you paste a live production token into an online decoder that sends it to a server — for logging, analytics, or worse — that token can be replayed against your API until it expires. Treat a JWT with the same care as a password that auto-expires.
- Prefer client-side decoders — tools that decode with JavaScript in your browser and make no network request with the token. The Dev Brains AI JWT Decoder works this way.
- Check the network tab — open DevTools while decoding; you should see zero requests containing the token.
- Use expired or staging tokens — when sharing a token in a bug report or Slack message, use one from a test environment or one that has already expired.
- Rotate if leaked — if a live token was pasted somewhere questionable, revoke the session or wait out the expiry, and rotate the signing secret if the token was long-lived.
Quick Reference: Safe JWT Decoding Checklist
- Split on . — expect exactly three parts
- Convert base64url to base64 (- to +, _ to /) and re-add = padding
- Decode header and payload only — the signature is binary
- Remember: expiry (exp) is in unix seconds, so multiply by 1000 for JavaScript dates
- Never make authorization decisions from decoded-but-unverified claims
- Only paste tokens into client-side tools, and prefer non-production tokens
Frequently Asked Questions
Split the token on the dot character, take the first two parts, convert base64url to base64 by replacing - with + and _ with /, then base64-decode and JSON.parse the result. The signature (third part) is binary and cannot be decoded to JSON.
Only if the tool decodes entirely in your browser and never sends the token to a server. A JWT is a live credential — anyone who captures it can impersonate you until it expires. Use client-side tools like the Dev Brains AI JWT Decoder, and prefer expired or test tokens when possible.
No. Decoding only reads the base64url-encoded header and payload, which anyone can do. Verification requires checking the cryptographic signature against the secret or public key on the server. Never trust claims from a token you have only decoded.