What Does "Decode JWT Token" Actually Mean?
Decoding a JWT token is the process of taking the compact xxx.yyy.zzz string and extracting the human-readable JSON that lives inside its header and payload segments. The token itself is not encrypted — decoding is a purely mechanical Base64URL operation that works without any secret, password, or signing key.
The typical reason to decode a JWT is inspection during development: you want to see which user id (sub claim) the token represents, when it expires (exp), what roles or scopes it grants, or which auth server issued it (iss). All of that information sits in plain JSON the moment you Base64URL-decode the payload.
Anatomy of a JWT Token
Here is a real (expired) JWT token, split for readability:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkYSIsImlhdCI6MTUxNjIzOTAyMn0
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cDecoded:
- Header:
{"alg":"HS256","typ":"JWT"} - Payload:
{"sub":"1234567890","name":"Ada","iat":1516239022} - Signature: HMAC-SHA256 of
base64url(header).base64url(payload)using the shared secret.
Where to Find JWT Tokens in Real Apps
- Authorization header —
Authorization: Bearer eyJhbG...in every API request after login. - Cookies — Some apps store the JWT in an HTTP-only cookie (
access_token,session, orid_token). - Local storage / session storage — Common in SPAs; open DevTools → Application → Storage.
- Query strings — Rare but seen in OAuth redirects:
...?access_token=eyJhbG... - OIDC id_token — Issued by Google, Auth0, Keycloak. Contains user profile claims.
Decode JWT Tokens Programmatically
JavaScript / Browser
function decodeJwt(token) {
const [headerB64, payloadB64] = token.split('.');
const decode = (s) => JSON.parse(
atob(s.replace(/-/g, '+').replace(/_/g, '/'))
);
return {
header: decode(headerB64),
payload: decode(payloadB64),
};
}
const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMifQ.abc';
console.log(decodeJwt(jwt));
// { header: { alg: 'HS256', typ: 'JWT' }, payload: { sub: '123' } }Node.js (using jose library)
import { decodeJwt, decodeProtectedHeader } from 'jose';
const header = decodeProtectedHeader(token);
const payload = decodeJwt(token);
// decodeJwt does NOT verify — use jwtVerify() for thatPython (using PyJWT)
import jwt
# Decode without verifying signature (inspection only)
payload = jwt.decode(token, options={"verify_signature": False})
print(payload) # {'sub': '123', 'exp': 1735689600, ...}
# Get header separately
header = jwt.get_unverified_header(token)Command Line (no dependencies)
# Decode just the payload
echo "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null | jq
# Base64URL sometimes lacks = padding; pad manually if base64 -d errors:
padded="$(echo "$JWT" | cut -d. -f2)$(printf '=%.0s' $(seq 1 $((4 - $(echo -n "$JWT" | cut -d. -f2 | wc -c) % 4))))"
echo "$padded" | base64 -d | jqCommon Debugging Scenarios
"My API returns 401 Unauthorized"
Decode your JWT and check the exp claim. If the timestamp is in the past, the token has expired and your client needs to hit the refresh-token endpoint to get a new one. Also check aud — some auth servers issue tokens with a specific audience, and the API rejects tokens meant for a different service.
"My user has admin role but the API says forbidden"
Decode the payload and look for the claim your API checks (often role, scope, permissions, or a namespaced claim like https://myapp.com/role). If it's missing or wrong, the problem is upstream — the auth server did not include the claim you expected.
"Signature verification fails in my backend"
Decode the header and check the algvalue. HS256 means shared-secret HMAC — you need the exact same secret string on both sides. RS256/ES256 mean asymmetric — the verifier needs the issuer's public key, usually fetched from a JWKS URL like https://issuer.com/.well-known/jwks.json. Also confirm the kid (key id) in the header matches a key in the JWKS.
Security Reminders
- Never trust a decoded JWT payload for auth decisions — always verify the signature first.
- Do not put passwords, credit card numbers, or PII in the payload. JWTs are not encrypted.
- Reject
alg: nonetokens and enforce a strict allow-list of accepted algorithms. - Use short expiry times (5–15 min access tokens, longer refresh tokens).
- Store JWTs in HTTP-only cookies when possible to reduce XSS exposure.
Related JWT Tools
- JWT Decoder Online — general-purpose online decoder walkthrough
- JWT Decoder in JavaScript — code recipes for browser & Node.js
- JWT Decoder in Python — PyJWT decode and verify guide
- Verify JWT Signature — HS256/RS256 verification walkthrough
- Base64 Encode — the encoding JWT is built on