Developer

JWT Decoder and Signature Checker

Paste a token and you get the header, the payload and every claim spelled out, with the timestamps converted to real dates. If you have the secret or the public key, the signature check runs here too — the token is never sent anywhere.

Three parts, two of them public

A JWT is three chunks joined by dots: header, payload and signature. The first two are base64url — base64 with - and _ in place of + and /, and the = padding stripped so the token survives being put in a URL.

Base64url is an encoding, not encryption. Anyone holding the token can read every claim in it, including the user in whose browser it is stored. This is the single most common misunderstanding about JWTs, and it has leaked plenty of internal user IDs, email addresses and role flags. If a value must stay secret, it does not go in a token.

The signature is the only part that needs a key, and all it proves is that the header and payload have not been altered since they were signed. It says nothing about who is holding the token now. A stolen JWT is a valid JWT.

The alg field is a claim, not a fact

The header names the algorithm the verifier should use, and the header is supplied by whoever sent the token. Two classic attacks come straight out of that.

alg: none. The specification includes an "unsecured" mode with an empty signature. A library that trusts the header will happily accept a token an attacker wrote by hand. Any token arriving with none is marked unsigned above, and no production verifier should allow it.

RS256 downgraded to HS256. If a server is configured for RS256 and verifies whatever the header asks for, an attacker can re-sign the token with HS256 using the server's own public key as the HMAC secret. The public key is public, so this costs nothing. The fix, in every library, is to pin the expected algorithm rather than reading it from the token.

Reading the timestamps

exp, nbf and iat are NumericDate values: seconds since 1 January 1970 UTC. Not milliseconds. Passing Date.now() straight into exp produces a token that expires around the year 56000, which every verifier accepts and nobody notices. The table above flags a value that looks like milliseconds.

exp is when the token stops being valid, nbf is when it starts, and iat is when it was issued. Verifiers usually allow a small clock skew in both directions, commonly 60 seconds, because server clocks drift.

Why you cannot cancel a token

A JWT is self-contained on purpose: the server checks the signature and the timestamps, and needs no database lookup. That is the whole performance argument for using one. The cost is that logging someone out, revoking a role or banning an account cannot take effect until the token expires, because nothing is consulted at verification time.

The practical answers are all compromises. Short expiry with a refresh token moves the lookup to the refresh step. A deny list of revoked jti values reintroduces the database you were avoiding. A token-version claim checked against the user record does the same. There is no version of this that keeps the statelessness and gets instant revocation.

Where this tool stops

It will not fetch the issuer's keys. A real verifier reads kid from the header and pulls the matching key from the issuer's JWKS endpoint; this page makes no network requests at all, so you have to paste the key yourself. Copying one key object out of a JWKS response works — a whole set with several keys is rejected rather than guessed at.

Encrypted tokens are out of scope. A JWE has five parts instead of three and the payload is ciphertext; the tool detects the shape and says so rather than showing you noise.

Checking a signature is not the same as accepting a token. Nothing here validates that iss and aud are the values your application expects, and those checks matter: a perfectly valid token issued for a different audience is still the wrong token.

One gotcha with HMAC secrets. Some issuers store the secret base64-encoded and decode it before signing. If the text you paste verifies nowhere, tick "Secret is base64" and try again — the bytes being hashed are different, and that alone changes the result.

Frequently asked questions

Is it safe to paste a production token here?

The token stays in your browser: this page is static files with no server behind it, and the decoding and signature check run in JavaScript on your machine. The honest caveat is that a token in your clipboard and browser history is already less private than one that stayed in an HTTP header, so prefer an expired or test token when you have the choice.

Can you decode a JWT without the secret?

Yes, and so can anyone else. The header and payload are base64url-encoded, not encrypted. The secret is only needed to confirm that the contents were not changed after signing.

Why does my signature check fail with the right secret?

Usually the bytes differ. A secret stored base64-encoded has to be decoded before hashing, so tick the base64 box. Also check for a trailing newline or space copied along with the secret, and make sure you are checking the same token, since re-signing changes the third part.

Which algorithms can this check?

HS256, HS384 and HS512 with a shared secret, and RS, PS and ES variants at 256, 384 and 512 with a public key in PEM SPKI form or as a JWK. EdDSA is not supported, because browsers do not offer it through the Web Crypto API everywhere yet.

What does "expired" mean here?

That the exp claim is in the past according to your own device clock. A server may still reject a token your clock thinks is fresh, or accept one a minute past expiry, since most verifiers allow a small amount of clock skew.

Last updated September 19, 2026