tokenbench

JWT Validator

Check a token's signature against a secret, a PEM key, a JWK or a JWKS. Signature and expiry are judged separately, because they fail separately.

Runs entirely in your browser.Open devtools → Network. Paste a token. Nothing is sent.Read the source · What we measure

Accepts SPKI and PKCS#1 PEM, certificates, a bare JWK, or a full JWKS.

Two verdicts, not one

Most online validators show a single green or red result. That is a mistake, because a JWT can fail in two independent ways and the fix for each is completely different:

The second case is overwhelmingly the common one in practice, and it is the case a single-verdict tool renders most confusing. This validator always shows both rows.

Verifying properly on your server

Checking a signature is necessary but not sufficient. A correct verification does all of the following:

  1. Pin the algorithm. Decide which algorithms you accept before you look at the token, and reject anything else. Never let the token's own alg header choose the verification method — that is how algorithm confusion works.
  2. Verify the signature against a key you already trust.
  3. Check exp and nbf, allowing a small clock skew (60 seconds is typical).
  4. Check iss equals the issuer you expect.
  5. Check aud contains your service. A token minted for a different audience must not be accepted, even if the signature is perfect.

The algorithm-confusion attack

This is the classic JWT vulnerability, and the reason this tool refuses to verify an HMAC token with a public key.

Suppose your server issues RS256 tokens. RS256 is asymmetric: you sign with a private key, and anyone can verify with the public key — which, being public, the attacker also has. Now suppose your verification code reads the algorithm out of the token header and dispatches on it, like this:

// VULNERABLE — never do this.
const header = JSON.parse(base64url.decode(token.split('.')[0]));
jwt.verify(token, publicKey, { algorithms: [header.alg] });

The attacker forges a payload, sets the header to {"alg":"HS256"}, and signs it with HMAC-SHA256 using your public key's bytes as the shared secret. Your server dutifully reads alg: HS256, treats the public key as an HMAC secret, computes the same MAC — and the forgery verifies. The attacker has minted a valid token for any user they like, using nothing but public information.

The fix is one line, and it is to never trust the header:

// Correct — the algorithm is your decision, not the token's.
jwt.verify(token, publicKey, { algorithms: ['RS256'] });

The alg: none attack is the same failure in a simpler costume: the attacker declares there is no signature at all, and a library that trusts the header obliges. Both are prevented by the same rule — pin the algorithm server-side. You can mint tokens for both of these cases on the generator to confirm your server rejects them.

Keys, in every form they arrive in

This validator accepts, and auto-detects:

Carriage returns, indentation and stray whitespace are all tolerated. If you paste an EC key for an RS256 token, the error names that mismatch specifically rather than saying "invalid key".

Why JWKS fetching so often fails in a browser

A JWKS endpoint publishes the public keys an issuer signs with, so verifiers can look up a key by kid and rotate keys without redeploying. Fetching one from a web page usually fails, and it is worth being precise about why.

JWKS endpoints are consumed by servers. Almost none of them send an Access-Control-Allow-Origin header, so the browser's same-origin policy blocks a page like this one from reading the response. That is the browser working correctly, not the provider misconfiguring anything.

Other tools work around this by proxying the request through their own backend. We don't, on purpose: the moment a server of ours sits in the middle, "your token never leaves your browser" stops being true, and the entire premise of this site collapses. So when CORS blocks the fetch, we hand you the curl command and take the JSON by paste instead. It is one extra step, and it keeps the guarantee intact.

Common questions

My token is valid but expired — is the signature bad?

No. Those are two separate checks, and this validator reports them as two separate verdicts for exactly that reason. A correctly-signed expired token is the most common real state you will encounter: the signature is fine, the exp claim is in the past. A tool that collapses both into one red 'invalid' hides which of the two actually failed.

Why does my secret not work?

Nine times out of ten, encoding. A secret can be the literal text you see, or the base64 decoding of that text into raw bytes — and the two produce completely different keys. Auth0, for example, historically stored base64-encoded secrets. Use the utf-8/base64 toggle; if verification fails with utf-8 and your secret looks base64, this tool will suggest the switch.

The JWKS fetch failed with a CORS error. Is your tool broken?

No — the identity provider is simply not sending CORS headers, which is normal and not a bug on their side. JWKS endpoints are designed to be read by servers, not browsers. Because this tool never proxies through a server of ours (that would defeat the privacy guarantee), the browser is blocked. Curl the URL and paste the JSON instead; the tool gives you the exact command.

Can I verify an HS256 token with a public key?

You should never want to. If your server does that, it is vulnerable to the algorithm-confusion attack: an attacker takes your public RSA key — which is public — signs a forged token with it as an HMAC secret, flips the header to HS256, and your server validates it. This tool refuses the operation and explains why rather than quietly doing it.

Which algorithms are supported?

All of them: HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512 and EdDSA (Ed25519). Everything runs through the browser's native WebCrypto implementation. EdDSA needs a current browser; if yours lacks it, the tool says so rather than failing mysteriously.

The rest of the workbench