JSON Web Tokens appear in HTTP headers, cookies, and query strings on virtually every modern web application. Yet most developers treat them as opaque blobs — something to pass around without inspecting. Knowing how to read a JWT safely is a core debugging skill, and it's easier than you think.
What Is a JWT Token?
A JSON Web Token is a compact, URL-safe string used to represent claims between two parties. Claims are statements about a user or system — things like "this user's ID is 42" or "this token expires at 5 PM."
JWTs are used heavily in:
- Authentication — after login, the server issues a JWT; the client sends it back with every request
- Authorization — the token carries the user's roles and permissions
- Information exchange — signed tokens let parties verify the data hasn't been tampered with
A critical distinction: JWTs are not secret by default. The payload is readable by anyone who has the token. What they provide is verifiability — not confidentiality.
The Three Parts of a JWT
Every JWT is three Base64URL-encoded sections joined by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header (first part) — the algorithm and token type:
{
"alg": "HS256",
"typ": "JWT"
}
Payload (second part) — the claims:
{
"sub": "1234567890",
"name": "Alice",
"iat": 1516239022
}
Signature (third part) — a cryptographic hash of the header + payload, signed with a secret key. This is the part you cannot forge without the server's secret.
How to Decode a JWT Safely
Decoding a JWT means base64url-decoding the header and payload to read the JSON. This reveals all the claims inside — user ID, roles, expiry time, issuer — everything your server put there.
The safe way: decode in your browser, locally, without sending the token anywhere.
Use DevZone's JWT Decoder to paste a JWT and instantly see the decoded header and payload. The tool runs entirely in your browser — the token never leaves your machine.
- Copy your JWT from your application's localStorage, cookie, or API response.
- Paste it into the decoder.
- Read the header to see the signing algorithm.
- Read the payload to see all claims.
What not to do: don't paste your JWT into random online tools, share it in Slack, or log it to the console in production. Even though the payload is readable, the full token can be replayed to authenticate as that user until it expires.
What You Can Read vs What You Can't Forge
Understanding this distinction is fundamental:
| What | Readable without secret? | Forgeable without secret? |
|---|---|---|
Header claims (alg, typ) |
Yes | No |
Payload claims (sub, name, exp) |
Yes | No |
| Signature | Yes (it's just bytes) | No |
Decoding is not verification. Reading the payload tells you what the token claims. Only the server, using the secret key, can verify whether those claims were actually signed by a trusted party.
This matters in practice: never trust a JWT on the client side without the server verifying the signature first.
Common JWT Claims Explained
The JWT specification defines a set of "registered claims" with specific meanings:
| Claim | Full name | Meaning |
|---|---|---|
sub |
Subject | The user or entity the token represents (usually a user ID) |
iss |
Issuer | The service that created the token |
aud |
Audience | The intended recipient (your API) |
exp |
Expiration | Unix timestamp after which the token is invalid |
iat |
Issued at | Unix timestamp when the token was created |
nbf |
Not before | Token is not valid before this timestamp |
jti |
JWT ID | A unique identifier for this token (used to prevent replay) |
Beyond registered claims, applications can add custom claims. A typical JWT might include role, email, org_id, or permissions depending on what the server needs clients to know.
When debugging authentication issues, the first things to check are:
- Is
expin the past? The token has expired. - Does
audmatch your API's expected audience? The token was issued for a different service. - Does
issmatch your trusted issuer? This token came from an unexpected source.
Decoding a JWT in Code
If you need to decode a JWT in your application code (not just read it):
JavaScript / TypeScript:
function decodeJwtPayload(token) {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(atob(base64));
}
Python:
import base64, json
def decode_jwt_payload(token):
payload = token.split('.')[1]
# Add padding if needed
payload += '=' * (4 - len(payload) % 4)
return json.loads(base64.urlsafe_b64decode(payload))
This decodes without verification — useful for logging or debugging, never for authorization decisions.
For production use, always verify with a library (jsonwebtoken in Node, PyJWT in Python, golang-jwt in Go) using your signing key.
FAQ
Can I decode a JWT without the secret key?
Yes. The header and payload are just Base64URL-encoded JSON — anyone can decode them. You only need the secret key to verify the signature, which confirms the token hasn't been tampered with.
Is it safe to decode a JWT on the client side?
Yes — reading claims from a JWT you already have is safe. Just don't confuse decoding (reading) with verification (trusting). Client-side code should never make authorization decisions based on decoded JWT claims alone; that must happen server-side.
Why is my JWT so long?
JWT size grows with the number of claims. Every field in the payload adds bytes. If your tokens are very large (over 8 KB), browsers may reject them in cookies. Consider reducing the claims to only what's necessary.
What's the difference between HS256 and RS256?
HS256 uses HMAC-SHA256 with a shared secret — the same key signs and verifies. RS256 uses RSA with a private/public key pair — the server signs with the private key, and anyone can verify using the public key. RS256 is better for distributed systems where multiple services need to verify tokens.