JWT Validator (HS256)

Verify an HS256 JWT signature with your secret and check the token was not tampered with.

JWT Validator: Verify a JWT Signature with a Secret, Public Key or JWKS

Paste a token, then paste the key it was signed with and press Verify. The validator reads the algorithm from the token's header and checks the signature with the Web Crypto API in your browser. For HS256, HS384 and HS512, the key is the shared secret. For RS256, PS256, ES256, EdDSA and their 384 and 512 variants, paste the public key as a PEM block, a single JWK, or a whole JWKS, from which the key is picked by the token's `kid`. After the signature, the validator checks the `exp` and `nbf` times and lists the issuer and audience so you can compare them with what your API expects.

What verification proves

A JWT's signature covers the first two parts of the token, header.payload. Verifying it proves two things: the token was created by someone holding the signing key, and not a single character of the header or payload has changed since. That is the only reason an API can trust claims such as sub or role; decoding alone proves nothing, because anyone can create a token with any payload.

A valid signature is necessary but not sufficient. RFC 7519 and the JWT Best Current Practices in RFC 8725 also require checking that the token has not expired (exp), is already valid (nbf), was issued by the expected issuer (iss) and is intended for your API (aud). A correctly signed token for another application, or one that expired last week, must still be rejected.

Which key to paste

HS256 / HS384 / HS512   the shared secret, exactly as the issuer uses it
RS256 / RS384 / RS512   RSA public key
PS256 / PS384 / PS512   RSA public key (RSA-PSS signatures)
ES256 / ES384 / ES512   EC public key on P-256 / P-384 / P-521
EdDSA                   Ed25519 public key

-----BEGIN PUBLIC KEY-----            PEM (SPKI), as printed by openssl
{ "kty": "RSA", "n": "...", "e": "AQAB", "kid": "..." }     a single JWK
{ "keys": [ {...}, {...} ] }          a JWKS; the key whose kid matches the token is used

Identity providers publish their public keys as a JWKS: Auth0, Okta and Keycloak list the URL as jwks_uri in /.well-known/openid-configuration, and AWS Cognito serves it at https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json. Open that URL, copy the JSON and paste it as the key.

Two PEM formats are not accepted directly, and the validator says so. A certificate (BEGIN CERTIFICATE) must be reduced to its public key with openssl x509 -in cert.pem -pubkey -noout. An older PKCS#1 RSA key (BEGIN RSA PUBLIC KEY) converts with openssl rsa -RSAPublicKey_in -in key.pem -pubout.

Reading the result

Signature is valid means the token matches the key. Signature does NOT match means the key is wrong, the token was modified, or it was signed with a different key than you think: check the kid in the header against the JWKS, and for HMAC check that the secret has no extra spaces or newline and is not meant to be Base64-decoded first, which some providers require.

If the signature is valid but the token is expired or not yet valid, the result is still marked as failed, with the time shown. In production, validators usually allow a small clock skew, often 30 to 60 seconds, between the issuing server and yours. The list of claims below the result shows exp, nbf, iat, iss and aud so you can compare them with your API's configuration.

Two other messages point straight at the cause. exp: not set, the token never expires means the issuer left out exp, which most APIs should refuse. And if the token is HS256 but you pasted a PEM block, the validator tells you the whole PEM text was used as a shared secret: the token's alg does not match the key you have. The HMAC secret is used exactly as typed, as UTF-8 text; it is not Base64-decoded first.

Algorithm attacks to avoid

Never accept alg: none. A token with that header has no signature, and some early libraries accepted it as valid. The validator refuses it outright, and your server should allow only the algorithms it expects.

Algorithm confusion is the subtler attack. If a server verifies with "whatever algorithm the header says" and holds an RSA public key, an attacker can create a token with alg: HS256 and use the public key, which is public, as the HMAC secret. A vulnerable library then computes a matching HMAC and accepts a forged token. The fix is to configure the expected algorithm on the server, as every modern library allows:

// Node.js, jose
const { payload } = await jwtVerify(token, JWKS, {
  algorithms: ['RS256'], issuer: 'https://auth.example.com/', audience: 'my-api',
});

# Python, PyJWT
jwt.decode(token, key, algorithms=["RS256"], audience="my-api", issuer="https://auth.example.com/")

HMAC secrets

RFC 7518 requires an HMAC key at least as long as the hash output: 256 bits, 32 random bytes, for HS256, and 512 bits for HS512. Short, human-chosen secrets such as secret or the company name can be brute-forced offline from a single token, because an attacker can test guesses against the signature without contacting your server. Generate the secret randomly, for example with openssl rand -base64 32.

Every service that verifies an HS256 token also holds the key that creates tokens. If several services need to verify tokens, asymmetric algorithms are the better fit: the issuer keeps the private key, and the others only get the public key, which cannot sign anything.

Privacy

Public keys are public, so pasting a JWKS here costs nothing. An HMAC secret is different: anyone who has it can mint valid tokens for every service that trusts it. The check runs with the browser's Web Crypto API and nothing is uploaded, but if a production secret has ever sat in a ticket, a chat or a shared screen, rotate it.

Frequently asked questions

How do I verify a JWT?

Paste the token, paste the secret (for HS256) or the public key as PEM, JWK or JWKS (for RS256, ES256 and others), and press Verify. The algorithm is taken from the token header, and exp and nbf are checked too.

Where do I find the public key to verify a JWT?

Identity providers publish a JWKS. Its URL is the jwks_uri in the provider's /.well-known/openid-configuration document. Paste the whole JWKS; the key matching the token's kid is used.

Why does JWT signature verification fail?

The key does not match: a wrong secret, whitespace in the secret, a secret that should be Base64-decoded, a JWKS without the token's kid, or a token that was modified. Check the kid and alg in the header first.

Is a JWT valid if the signature is correct?

Not necessarily. It must also be within its exp and nbf times and have the issuer and audience your API expects.

What is the difference between HS256 and RS256?

It decides what you paste as the key. An HS256 token is checked with the same shared secret that signed it. An RS256 token is checked with the issuer's public key, as PEM, JWK or JWKS, and the private key never has to leave the issuer.

Are my token and keys sent to a server?

No. The signature is verified with the Web Crypto API in your browser, and nothing is uploaded or stored.