
JWT Decoder: Decode and Verify Tokens in Your Browser
Decode a JSON Web Token, read its claims and expiry, and verify an HS256, RS256 or ES256 signature against a key you supply. Everything runs in your browser.
- Paste a token, or load the sample one.
- Read the header, the claims and the expiry as they decode.
- Add the secret or public key to check the signature.
JWT reference: claims, signatures and common errors
The decoder above splits a token into its header, payload and signature, reads the claims, and checks the signature against a key you paste. It all happens in your browser. Below it: what the parts mean, which errors matter, and how to test token handling in CI. This page is part of our API security testing guide.
What Is a JWT?
A JWT is a set of claims, written as a JSON object, carried inside a signed or integrity-protected structure. Most tokens you will meet are the compact JWS form: three Base64URL segments joined by periods, written as `header.payload.signature`. A JWT can also arrive as an encrypted JWE, which has five parts and hides its payload. This tool handles the three-part form (RFC 7519 introduction, RFC 7515 section 3.1).
Take the sample token. Its first segment decodes to `{"alg":"HS256","typ":"JWT"}`, the header, which names the signing algorithm. The second is the payload: who issued the token, who it is for, when it expires. The third is the signature over the first two, and it is the only part that needs a key. For how tokens are issued and validated end to end, read our complete JWT guide.
How JWT Decoding and Signature Verification Work
Decoding is Base64URL, not decryption. Anyone holding the token can read the header and the payload without a key. Decoding proves nothing about who issued the token or whether somebody edited it in transit. Auth0's `jwt-decode` library says so in its own README: it decodes, it does not validate (RFC 7515 sections 3.1 and 5.2).
Verification is the separate step. The issuer signs the exact string `header.payload`, both segments still encoded. The verifier repeats that computation over the bytes it received and compares the result with the third segment. Change one character of the payload and the signature stops matching.
Three algorithms cover most bearer tokens crossing an API authentication boundary (RFC 7518 section 3.1):
HS256 is HMAC with SHA-256. One shared secret both signs and verifies, so anyone who can verify can also mint tokens.
RS256 is RSASSA-PKCS1-v1_5 with SHA-256. The private key signs, the public key verifies.
ES256 is ECDSA on the P-256 curve with SHA-256. Same split, shorter keys and signatures.
Public keys usually arrive as a JWK, a JSON object that describes one key. `kty` is required and names the key type, `use: sig` marks the key for signatures, and `kid` names one key inside a JWK Set so an issuer can rotate keys without breaking live tokens (RFC 7517 sections 4 and 5).
The decoder on this page verifies with WebCrypto, the browser's own crypto engine. Your token, secret and key stay in the page: nothing is uploaded, and nothing is written into the URL.
How to Read JWT Claims and Expiration
Seven claim names are registered in the standard. Everything else in a payload belongs to the application (RFC 7519 sections 4.1.1 to 4.1.7).
| Claim | Meaning |
|---|---|
iss | Issuer that created the token |
sub | Subject the token is about |
aud | Intended recipients, a string or an array |
exp | Expiry time |
nbf | Not valid before this time |
iat | Issued at time |
jti | Unique token id that helps stop replay |
Acceptance is a time check plus an audience check. The current time must be before `exp` and at or after `nbf`. If `aud` is present and does not name the recipient, the token has to be rejected. Implementations may allow a small leeway for clock skew between the issuer and the verifier, usually no more than a few minutes (RFC 7519 sections 4.1.3 to 4.1.6). The skew input above the claims table applies that leeway.
None of these claims is mandatory in the base standard, which is why the tool reports a claim as missing rather than invalid. Whether a missing `iss` or `aud` is a failure depends on your policy. OpenID Connect ID tokens tighten it up and require both. OAuth 2.0 access tokens are a different case: many are JWTs, but the standard never says they must be, so an opaque one will not decode here.
Common JWT Decoder Errors
Wrong number of parts. A truncated copy or a stray line break. Three parts is the signed form.
Five parts. That is an encrypted JWE, not a JWS. Its payload is ciphertext and stays unreadable without the decryption key (RFC 7516 section 3.1).
Invalid Base64URL. Usually a `+` or `/` copied from standard Base64, which uses a different alphabet.
Valid Base64URL, invalid JSON. The segment decoded, but it is not a JSON object.
Missing `alg`. The header has to name the algorithm; without it a verifier has nothing to check against (RFC 7515 section 5.2).
A time claim as a string. `exp`, `nbf` and `iat` are NumericDate numbers. `"1780000000"` is not `1780000000`.
Empty signature under a signed `alg`. Something stripped the signature on the way.
Unsupported algorithm. The name is a real JWA name, but this tool verifies HS256, RS256 and ES256 only. Decoding still works.
Decoded fine, but my API rejects it
Eight causes explain nearly every rejection: expiry, `nbf`, audience, issuer, a missing scope or role, the wrong key, the wrong algorithm, and clock skew. The first five are claim checks you can settle from the table above. The last three are signature checks and need the key (RFC 7519 section 4.1, RFC 8725 section 3).
JWT Security Checks Testers Should Run
Readable is not trusted. A decoded claim is an unverified assertion until the signature check and your own policy check both pass. RFC 8725 section 3.3 is blunt about it: if any cryptographic operation fails, reject the whole JWT.
Two attacks target the verifier rather than the token:
The alg none attack. Strip the signature, set `alg` to `none`, and hope the verifier accepts the unsecured form. This tool marks such a token as unsigned and never reports it as verified.
RS256 to HS256 confusion. Re-sign the token with HMAC, using the issuer's public key as the secret. A verifier that trusts the token's own `alg` header treats that public key as a shared secret and lets the token through (RFC 8725 sections 2.1 and 3.1).
One defense covers both: an explicit list of accepted algorithms in the verifier, and one algorithm family per key. Never let the token pick. The decoder above follows the same rule, which is why pasting an RSA public key into the HS256 secret box returns an algorithm confusion warning.
Weak HMAC secrets are the third problem: a human-memorable symmetric key can be attacked offline once someone holds a single token (RFC 8725 section 2.2). Treat production tokens as credentials wherever you paste them. FusionAuth tells users not to paste production tokens into its decoder even though the processing is local. The wider set of API-side controls lives in our API security checklist.
How to Test JWT Validation in CI
One positive fixture is not a test suite. Pin a known-good token, then keep at least seven negatives beside it: an altered payload with the original signature, the wrong key, a disallowed algorithm, `alg: none`, an expired `exp`, a future `nbf`, the wrong `aud` and the wrong `iss`. Add two malformed inputs, broken Base64URL and broken JSON, for the parser edge. Only the known-good token may pass (RFC 7515 section 5.2, RFC 7519 section 4.1, RFC 8725 section 3).
Assert the API outcome, not only that a library threw. The JWT standards define acceptance, not an HTTP status code, so the exact status belongs to your application's contract. What has to hold: the request was refused and no protected data came back.
const cases = [
{ name: 'valid token', token: fixtures.valid, rejected: false },
{ name: 'payload altered', token: fixtures.tampered, rejected: true },
{ name: 'wrong signing key', token: fixtures.wrongKey, rejected: true },
{ name: 'alg none', token: fixtures.algNone, rejected: true },
{ name: 'alg swapped', token: fixtures.algSwap, rejected: true },
{ name: 'expired', token: fixtures.expired, rejected: true },
{ name: 'nbf in the future', token: fixtures.notYetValid, rejected: true },
{ name: 'wrong audience', token: fixtures.wrongAud, rejected: true },
{ name: 'wrong issuer', token: fixtures.wrongIss, rejected: true },
{ name: 'malformed token', token: 'abc.def.ghi', rejected: true },
];
for (const c of cases) {
test(c.name, async () => {
const res = await get('/orders', { authorization: 'Bearer ' + c.token });
assert.equal(res.status >= 400, c.rejected);
if (c.rejected) assert.equal(res.body.orders, undefined);
});
}
Next step: the wider suite these fixtures belong in is laid out in the API security testing guide.
How Qodex Tests Token Handling
Qodex runs authentication scenarios against your real endpoints on every change. It sends expired tokens, tampered payloads, wrong-audience tokens and algorithm-swapped tokens at protected routes, and checks that each one is refused rather than served.
When a check fails, it arrives with the request and response attached, so you can see which token was sent and what the API returned.
Start a free trial and run these token checks against your own API, or see how Qodex security testing works.
Frequently Asked Questions
Can a JWT be decoded without a secret?
Yes. The header and payload are Base64URL encoded, not encrypted, so any holder of the token can read them. The key only matters for the signature check.
Does decoding a JWT verify its signature?
No. Decoding reads the two encoded segments. Verification is a separate cryptographic check over them, and it needs the signing secret or the issuer public key.
Is it safe to paste a JWT into this decoder?
Everything runs in your browser and nothing is uploaded. Even so, a production token is a live credential: prefer a test token, and rotate anything you paste anywhere.
How can I tell whether a JWT is expired?
Compare exp, which is seconds since 1970 in UTC, with the current time. The decoder shows the UTC time, your local time and the difference, so nothing needs converting by hand.
What is clock skew in JWT validation?
The small difference between the issuer clock and the verifier clock. RFC 7519 lets an implementation allow leeway for it, usually seconds to a few minutes, before calling a token expired.
Why does a JWT signature fail verification?
Four usual causes: the wrong key, the wrong algorithm, a header or payload changed after signing, or a Base64URL-encoded secret pasted as plain text. The tool has a checkbox for the last one.
What does alg none mean, and why is it risky?
It means the token carries no cryptographic signature. A verifier that accepts alg: none trusts anything anyone sends, which is the alg none attack described in RFC 8725.
How do I verify RS256 or ES256 with a JWK?
Paste the issuer public JWK, the one with kty RSA or EC and no d member. The tool imports it with WebCrypto and checks the signature. Private keys are refused.
Related Articles



Test how your API handles tokens
Qodex sends expired, tampered and algorithm-swapped tokens at your real endpoints on every change, and shows the request and the response when one gets through.