Last month I watched a colleague paste a production JWT into the first Google result for "jwt decode online." I opened DevTools on that site. The token — with real user IDs, roles, and an expiry 24 hours out — was sent to their backend in a POST request.
He had no idea.
This happens constantly. Developers treat online decoders like calculators — paste in, get result, move on. But a JWT often contains session data, user identifiers, permissions, and sometimes PII. Sending it to a third-party server is a security incident waiting to happen.
Here's how to decode a JWT safely, what to actually look for when you do, and how to verify the claims that matter.
What's Actually Inside a JWT
A JWT has three parts separated by dots: header.payload.signature
Each of the first two parts is just Base64URL-encoded JSON. There is no encryption involved in a standard JWT — anyone with the token can read the header and payload. The signature verifies integrity, not confidentiality.
This is the part that surprises people: JWTs are not encrypted by default. If your JWT contains an email address, anyone who intercepts it can read that email.
Step 1: Decode It Locally
You don't need a website. You can decode a JWT in your browser's console right now:
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
const [header, payload] = token.split('.').slice(0, 2).map(
part => JSON.parse(atob(part.replace(/-/g, '+').replace(/_/g, '/')))
);
console.log('Header:', header);
console.log('Payload:', payload);
That's it. No npm package. No website. Pure browser JavaScript.
If you want something more visual with syntax highlighting and expiration checks, Anytools JWT Decoder does exactly this — entirely client-side. Open your Network tab while using it and you'll see zero outbound requests after page load.
Step 2: Check the Header
The header tells you the signing algorithm:
{
"alg": "HS256",
"typ": "JWT"
}
Things to watch for:
-
alg: "none"— This is the classic JWT vulnerability. If your backend acceptsalg: none, anyone can forge tokens. This should never appear in production. -
alg: "HS256"with a public key — If your system uses RS256 (asymmetric) but the token says HS256 (symmetric), someone might be trying an algorithm confusion attack. -
Missing
typ— Not a vulnerability, but sloppy. Indicates the issuing system might have other configuration issues.
Step 3: Read the Payload Claims
Standard claims to check:
| Claim | What it is | What to look for |
|---|---|---|
exp |
Expiration time | Is it unreasonably far in the future? Tokens valid for 30 days are a red flag. |
iat |
Issued at | Does this match when the token was actually created? |
nbf |
Not before | Is the token being used before it's valid? |
iss |
Issuer | Does it match your expected auth server? |
aud |
Audience | Is this token intended for your service? |
sub |
Subject | The user identifier. Is PII leaking here? |
Step 4: Check for PII in the Payload
This is the one most teams skip. Search the payload for:
- Email addresses
- Full names
- Phone numbers
- Internal user IDs that map to real identities
- IP addresses
- Role names that reveal your internal permission model
If any of these are present, ask whether they need to be. JWTs are often stored in local storage, sent in URLs, and logged by proxies. Every claim in the payload is a potential data leak.
Step 5: Verify the Expiration Makes Sense
Convert the exp claim from a Unix timestamp to a human-readable date. A quick way:
new Date(1516239022 * 1000).toISOString()
// "2018-01-18T01:30:22.000Z"
Or use a timestamp converter if you want instant conversion without doing mental math.
Common issues:
-
Tokens that never expire — no
expclaim at all - Tokens valid for days or weeks — access tokens should typically expire in minutes to hours
- Refresh tokens in the JWT format — these should be opaque, not JWTs
The Takeaway
Decoding a JWT doesn't require trust. The data is just Base64-encoded JSON. You can do it in a browser console, in a local script, or in a client-side tool that doesn't make network requests.
What you should never do is paste production tokens into a website that processes them server-side. If you can't verify that a decoder is client-side only (by checking the Network tab), don't use it.
The habit takes 30 seconds to build. Open DevTools. Watch the Network tab. If you see a POST request with your token in the body, close that tab and don't go back.
Top comments (0)