JWTs Explained: What's Inside That Token and How to Read It
If you've built any web app with authentication in the last five years, you've almost certainly used JWTs (JSON Web Tokens) — even if you didn't realise what was happening under the hood.
They appear in Authorization: Bearer <token> headers, in cookies, in URL parameters. They're the backbone of OAuth 2.0, OpenID Connect, and most modern single-sign-on systems.
But many developers treat them as opaque blobs. You get a JWT from your auth server, you forward it to the API, it works. Magic.
The problem: when something breaks — expired token, wrong claims, missing roles — you're stuck. You don't know what's in the token, so you can't debug it.
This guide will show you exactly what's inside a JWT and how to read one in seconds.
What is a JWT?
A JWT is a compact, URL-safe string made of three Base64URL-encoded parts, separated by dots:
header.payload.signature
Here's a real example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
That looks like gibberish. But it's just three pieces of JSON, encoded in Base64URL.
Part 1: The Header
Decode the first segment (eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9) from Base64 and you get:
{
"alg": "HS256",
"typ": "JWT"
}
The header tells you:
-
alg: The signing algorithm — HS256 (HMAC-SHA256), RS256 (RSA-SHA256), or ES256 (ECDSA). This matters for verification. -
typ: AlwaysJWT.
Security note: The alg: none attack is real. A malicious actor can craft a JWT with "alg": "none" and no signature. Your server should explicitly reject this.
Part 2: The Payload
Decode the second segment and you get the actual data — called claims:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
Claims are categorised as:
Registered claims (standardised, optional but recommended):
| Claim | Full name | Meaning |
|-------|-----------|---------|
| iss | Issuer | Who created this token |
| sub | Subject | Who this token is about (usually a user ID) |
| aud | Audience | Who should accept this token |
| exp | Expiration | Unix timestamp when the token expires |
| iat | Issued at | Unix timestamp when the token was created |
| nbf | Not before | Token is not valid before this time |
| jti | JWT ID | Unique identifier for this token |
Public claims: Custom claims you define, like role, permissions, email.
Private claims: Application-specific claims shared between parties.
The exp claim is the most important for debugging. If it's in the past, your token has expired.
Part 3: The Signature
The third part is a cryptographic signature of header + "." + payload, using the algorithm specified in the header.
This part cannot be decoded to meaningful data — it's binary. But it's crucial:
- It proves the token wasn't tampered with
- It can only be verified by the party that has the secret key (for HS256) or the public key (for RS256/ES256)
Key point: You can read the header and payload without any key. But you cannot verify the signature without the server's key. Never trust a JWT's contents for security decisions on the client side — always verify server-side.
How to Decode a JWT Instantly
The fastest way: paste your JWT into the SnappyTools JWT Decoder.
It shows you:
- Decoded header and payload (pretty-printed JSON)
- Expiry countdown (how many seconds until the token expires, or how long ago it expired)
- All
iat,exp,nbftimestamps converted to human-readable dates - Whether the token is expired or valid
No key required to decode — only to verify.
Debugging JWT Issues: Common Problems
"Token expired"
Check the exp claim. Convert it from Unix timestamp to a date. If it's in the past, your client isn't refreshing tokens properly.
"Invalid audience"
Check the aud claim against what your server expects. A token issued for api.yourapp.com will be rejected by admin.yourapp.com if they check aud.
"Insufficient permissions"
Look for custom claims like role, scope, or permissions. Make sure the issuing server is adding them.
"Signature verification failed"
This usually means the secret/key used to sign the token doesn't match what the server is using to verify. Check for key rotation events.
JWTs vs Session Tokens
| JWT | Session Token | |
|---|---|---|
| Storage | Client (localStorage, cookie) | Server-side (database) |
| Verification | Cryptographic (stateless) | Database lookup (stateful) |
| Revocation | Hard (must wait for expiry) | Easy (delete from DB) |
| Scalability | Excellent | Requires shared session store |
| Payload | Carries user data | Just an ID |
JWTs shine for microservices and APIs where you don't want every service to call a session database. The trade-off: you can't invalidate a JWT before it expires (without additional infrastructure like a blocklist).
Security Best Practices
- Set short expiry times — 15 minutes for access tokens, longer for refresh tokens
- Always verify the signature server-side — never trust a JWT just because it decodes
-
Validate the
audandissclaims — don't accept tokens from unknown issuers -
Reject
alg: none— explicitly whitelist allowed algorithms - Never store sensitive data in the payload — it's readable by anyone
- Use HTTPS always — JWTs in transit can be intercepted
- Implement token refresh — short-lived access tokens + refresh tokens
Key Takeaway
A JWT is just a signed JSON object. The signature prevents tampering, but the contents are readable by anyone who has the token.
Next time you need to debug an auth issue, paste the token into a JWT decoder and read it — it'll tell you exactly what's happening.
SnappyTools JWT Decoder — decode any JWT token in your browser. See header, payload, expiry countdown, and human-readable timestamps instantly. 100% private — your token never leaves your browser.
Top comments (0)