How to Decode a JWT Without Verifying the Signature
A JSON Web Token (JWT) is just a Base64-encoded JSON object — the signature exists to verify the token's authenticity, but the payload itself is not secret. There are plenty of legitimate reasons to decode a JWT without verifying the signature: debugging, inspecting expiry time, reading the subject claim in a non-auth service, or checking what a third-party token contains.
Here's how to do it safely.
What a JWT looks like
A JWT has three parts separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- Part 1 (header): algorithm and token type
- Part 2 (payload): the actual claims — user ID, expiry, roles, etc.
- Part 3 (signature): HMAC or RSA signature over parts 1+2
The header and payload are just Base64URL-encoded JSON. You can decode them with no key at all.
Decoding in Python
import base64
import json
def decode_jwt_payload(token: str) -> dict:
# Split and take the payload (second segment)
payload_b64 = token.split(".")[1]
# Base64URL has no padding — add it back
padding = 4 - len(payload_b64) % 4
if padding != 4:
payload_b64 += "=" * padding
decoded_bytes = base64.urlsafe_b64decode(payload_b64)
return json.loads(decoded_bytes)
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
payload = decode_jwt_payload(token)
print(payload)
# {'sub': '1234567890', 'name': 'John Doe', 'iat': 1516239022}
The same approach works for the header — just use token.split(".")[0] instead.
Decoding in JavaScript (browser or Node.js)
function decodeJwtPayload(token) {
const base64Url = token.split('.')[1];
// Replace URL-safe chars and add padding
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonStr = atob(base64); // browser / Node 16+
return JSON.parse(jsonStr);
}
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
console.log(decodeJwtPayload(token));
// { sub: '1234567890', name: 'John Doe', iat: 1516239022 }
In Node.js before v16, use Buffer.from(base64, 'base64').toString('utf8') instead of atob.
Using a library (the production way)
For production code where you also need to verify the signature:
# pip install PyJWT
import jwt
# Decode WITHOUT verification (inspect only)
payload = jwt.decode(token, options={"verify_signature": False})
# Decode WITH verification (proper auth)
payload = jwt.decode(token, secret_key, algorithms=["HS256"])
// npm install jsonwebtoken
const jwt = require('jsonwebtoken');
// Decode without verification
const payload = jwt.decode(token);
// Verify and decode
const payload = jwt.verify(token, secretKey);
When is it safe to decode without verifying?
It's safe when:
- You're debugging — you want to inspect what's in a token without setting up the full verification stack.
- You're in a service that receives pre-verified tokens — e.g. an internal microservice behind an API gateway that already validated the token.
- You need to read non-sensitive claims like
exp(expiry) to decide whether to even attempt verification before making a network call.
It's not safe when:
- You're using the decoded claims to make authorization decisions (access control, permissions, payment flows). Always verify the signature first in these cases.
- The token came from an untrusted source. Anyone can construct a JWT with any payload — without signature verification, you can't know who issued it.
Reading the expiry claim
A common pattern is to check whether a token has expired before sending a request:
import time
payload = decode_jwt_payload(token)
exp = payload.get("exp")
if exp and exp < time.time():
print("Token is expired — refresh before use")
The exp claim is a Unix timestamp (seconds since epoch).
Key takeaways
- JWT payloads are Base64URL-encoded, not encrypted. Anyone can read them.
- Decode by splitting on
., taking segment 1, and Base64URL-decoding it. - Only skip signature verification for debugging or pre-verified contexts.
- For any authorization decision, always verify the signature with the correct key.