What verification proves — and what it doesn't
A valid signature proves that the header and payload were produced by someone holding the signing key and have not been modified since. It does not prove the token is still valid for your API. After the signature check you must still validate the claims: exp (not expired), nbf (already active), iss (the issuer you trust) and aud (intended for you). That is why this tool reports the signature and the claims separately — a token can be signature verified ✓ and expired ✕ at the same time.
Which key do I need?
| Token alg | Verify with | Accepted formats |
|---|---|---|
| HS256 / HS384 / HS512 | The same shared secret the issuer signed with | Text (UTF-8), Base64, Base64URL, hex |
| RS256 / RS384 / RS512 | The issuer's RSA public key | PEM (SPKI or PKCS#1), X.509 certificate, JWK, JWKS |
| PS256 / PS384 / PS512 | The issuer's RSA public key | Same as RS* |
| ES256 / ES384 / ES512 | The issuer's EC public key on P-256 / P-384 / P-521 | PEM, certificate, JWK, JWKS |
For identity providers (Auth0, Okta, Keycloak, Cognito, Entra ID, Firebase and others) the public keys are published as a JWKS, usually linked from the issuer's /.well-known/openid-configuration as jwks_uri. Paste the JWKS and the key whose kid matches the token header is selected automatically — or use Load a JWKS from a URL, which fetches only the key set, never your token.
Why verification fails
- Wrong secret encoding. Many servers store HMAC secrets Base64-encoded. If your code does
base64decode(secret)before signing, choose Base64 here too. - Key rotation. The token's
kidrefers to a key that is no longer (or not yet) in the JWKS you pasted. - Wrong environment. A staging token checked against the production key set, or vice versa.
- Modified token. Editing even one character of the header or payload invalidates the signature. That is the point of signing.
- DER-encoded ECDSA signatures. JWS requires raw
R || Ssignatures (64 bytes for ES256). Some libraries emit DER by mistake; the security checks flag unexpected signature lengths.
Verify in code — pin the algorithm
Never let the token choose its own algorithm. Every mainstream library lets you pass the list of allowed algorithms:
// Node.js / browsers — jose
const { payload } = await jwtVerify(token, publicKey, {
algorithms: ["RS256"],
issuer: "https://auth.example.com/",
audience: "api.example.com",
});
# Python — PyJWT
claims = jwt.decode(token, public_key, algorithms=["RS256"],
issuer="https://auth.example.com/", audience="api.example.com")The decoder generates ready-to-run snippets for JavaScript, TypeScript, Python, Java, Go, C# and PHP from any token you paste. For the full checklist read How to verify a JWT correctly.