A JWT is three base64url segments joined by dots: a header describing the signing algorithm, a payload of claims, and a signature over the first two. Anyone can decode the header and payload without a key — they are not encrypted, only encoded — which is the single most common misunderstanding about JWTs. Treating a JWT as a place to hide data instead of a place to sign data is the root cause of most JWT-related incidents.
The signature is what actually matters, and it is where most real-world JWT bugs live. The classic attack is alg:none: some early JWT libraries treated a header claiming 'alg: none' as an instruction to skip verification entirely, so an attacker could hand-craft a token with no signature at all and have it accepted. A related and subtler attack is algorithm confusion — if a server accepts both RS256 (asymmetric) and HS256 (symmetric with a shared secret) without pinning the expected algorithm, an attacker who obtains the server's public RSA key can resubmit it as the HMAC secret for an HS256 token, since the public key is, structurally, just a string the server will happily use as a MAC key.
Beyond the signature, expiry hygiene is where most tokens quietly go wrong. A token with no exp claim never expires by design — some session tokens intend this, but for anything bearer-token-shaped it usually means whoever issued it forgot. A token with an exp far in the future (30+ days is a common threshold) increases the blast radius of a leak: if that token ends up in a log file or a git history, it stays valid for a long time. And because JWT payloads are visible to anyone who intercepts the token — including in browser storage, proxy logs, and error-tracking tools — putting an email address, phone number, or anything SSN-shaped directly in a claim turns every place that token travels into a PII exposure.
There is one more check that goes beyond reading the token: whether its secret is guessable. An HS256 token is signed with a symmetric secret, and if a developer picked something like "secret", "password", "changeme", or a framework's placeholder default, anyone can recover that secret by re-computing the signature over the token's own header and payload with each candidate until one matches. Once the secret is known, an attacker can mint any token they like — flip a claim to admin:true, change the subject — and the server accepts it as genuinely signed. This decoder tests HS256 tokens against a list of common weak secrets right in your browser; if it finds a match, it shows you the recovered secret and a forged admin token signed with it, as concrete proof that the signature is protecting nothing. It only tests HS256 (not HS384/HS512), and a clean result means the secret is not on the list — not a guarantee that it is strong.
This decoder runs entirely in your browser, decodes both segments, humanizes the exp/iat/nbf timestamps against the current time, and checks the header and payload against the failure modes above before you ship the token anywhere.
Edge cases worth knowing
- An HS256 token signed with a guessable secret
- If a token is signed with a common secret like "secret" or a framework default, tryb.dev recovers it by re-signing the token's header and payload with each candidate from a public weak-secret list and comparing the result to the real signature. A match is proof-positive — it then shows the secret and a forged token that verifies under it. This runs only for HS256, and a non-match means "not on the list," not "provably strong."
- A token with alg set to none
- Some JWT libraries historically accepted a header of {"alg":"none"} and skipped signature verification. tryb.dev flags this as critical the moment it sees the header, regardless of what the payload or signature segment contains.
- HS256 header paired with an RSA-shaped secret
- If the string used as the HMAC key in an HS256 token looks like a PEM-encoded RSA public key (starts with -----BEGIN PUBLIC KEY-----), that's the algorithm-confusion pattern — the server likely also accepts RS256 and can be tricked into verifying with its own public key as a shared secret.
- A refresh token with no exp claim at all
- Not every long-lived token is a mistake — some refresh-token designs intentionally omit exp and rely on server-side revocation. tryb.dev surfaces this as a warning either way, because the decoder can't tell your intentional design from a forgotten claim; you have to make that call.
Common mistakes
- Assuming the payload is encrypted because it's base64 — it's just encoded, and anyone with the token can read every claim.
- Signing HS256 tokens with a short, memorable, or copy-pasted secret — if it's on a public wordlist, the signature protects nothing and anyone can forge tokens.
- Committing the signing secret to source control or leaving a framework's placeholder default ("your-256-bit-secret", "changeme") in production config.
- Accepting more than one signing algorithm on the server without explicitly pinning the expected one per token type.
- Storing session-critical data (roles, permissions) in the payload and trusting it without re-verifying the signature on every request.
- Setting exp far enough in the future that a leaked token stays exploitable for weeks.