How to Decode and Inspect a JWT Token (Without Sending It Anywhere)
Decode and read JWT token claims locally from the command line — no jwt.io, no third-party sites, no copy-pasting production tokens into a browser tab.
You’re debugging an auth issue. The token is right there in the request. Something’s wrong with the claims — you just can’t tell what without seeing inside it. Most engineers open a browser tab and paste it into jwt.io. There’s no need.
A JWT is three base64url-encoded segments. The header and payload are plain JSON — you can read them with a one-liner on any machine. No account, no browser, no sending your token anywhere.
What’s Actually Inside a JWT
Three parts separated by dots: header.payload.signature
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 ← header
.eyJzdWIiOiJ1c2VyXzEyMyIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyMDAwMDAwMH0 ← payload
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← signature
The header and payload are base64url-encoded JSON — readable with basic tools. The signature is a cryptographic value; you can’t decode it to get useful content.
Decode It Locally
With jq (cleanest — decodes header and payload in one command):
TOKEN="your.jwt.here"
echo $TOKEN | jq -R 'split(".") | .[0:2] | map(@base64d) | map(fromjson)'
Pure bash (no extra tools required):
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null
If you get a padding error, add | tr '_-' '/+' before base64 -d — base64url uses different characters than standard base64.
Python (cross-platform fallback):
python3 -c "
import base64, json
token = 'your.jwt.here'
payload = token.split('.')[1]
padding = '=' * (4 - len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload + padding)), indent=2))
"
Example output:
{
"sub": "user_123",
"role": "admin",
"exp": 1720000000,
"iat": 1719913600
}
Fields worth checking: sub (user ID), exp (expiry as Unix timestamp — run date -d @1720000000 to read it), iat (issued at), aud (audience), iss (issuer). Check the header’s alg field too — it tells you how the signature was created.
What Decoding Does NOT Tell You
This is the part most posts skip.
Decoding a JWT tells you what the token claims. It does not tell you whether those claims can be trusted.
Any JWT can be crafted with any payload. A token claiming
"role": "admin"means nothing until the signature is verified — without verification, you have no idea who signed it or whether it’s been tampered with.
One reason this matters: historically, some JWT libraries accepted tokens with "alg": "none" in the header — meaning no signature at all — allowing anyone to forge a token with arbitrary claims. Always check that alg is a real algorithm (RS256, ES256, HS256), never none.
When You Need to Verify (Not Just Decode)
Decoding is for inspection. If you need to confirm a token is authentic and untampered, use a proper verification step:
# jwt-cli: brew install jwt-cli or cargo install jwt-cli
jwt decode --secret "your-signing-secret" $TOKEN
Or reach for your language’s JWT library — they handle signature verification correctly where raw base64 decoding cannot.
Next time you hit a JWT auth issue, reach for the one-liner before opening a browser tab. If the payload looks right and auth is still failing, the problem isn’t the claims — it’s the signature verification step.