How to verify a JWT signature (not just decode it)

JWT and security 10 min read

How to verify a JWT signature (not just decode it)

August 2, 2026 · 10 min read

Paste any JWT into a decoder and you'll get back a header and a payload, in full, readable JSON - even if the token is a total forgery with a made-up signature. That's not a bug in the decoder; it's how JWTs are designed. The payload is Base64URL, not encrypted, so reading it requires no secret at all. Trusting it does.

Verification is the step that actually matters for security, and it's a completely separate operation from decoding.

Decoding is not verifying

A JWT is three Base64URL segments joined by dots: header.payload.signature. Decoding splits the string on those dots and Base64URL-decodes the first two segments back into JSON. It never touches the third segment at all - which means a decoder will happily show you the claims from a token whose signature is garbage, expired, or simply missing.

If your application logic reads decoded.payload.sub and treats it as "the authenticated user" without a prior verification step, you don't actually have authentication - you have a JSON parser trusting arbitrary user input.

How HS256 verification works

HS256 (HMAC-SHA256) is symmetric: the same secret both signs and verifies. To verify, you recompute the HMAC-SHA256 over the exact bytes base64url(header) + "." + base64url(payload) using your secret, then compare the result to the signature segment already on the token.

signature = HMAC-SHA256(secret, header_b64 + "." + payload_b64)
verified  = (signature === token_signature_segment)

If they match, two things are true: the token was signed by someone who knows the secret, and the header/payload haven't been altered since - flip a single character in the payload and the recomputed HMAC comes out completely different.

Verifying a token step by step

1. Split the token into its three segments and Base64URL-decode the header to check alg. 2. Confirm alg matches what you expect - if your server issues HS256 tokens, reject anything claiming a different algorithm outright (more on why below). 3. Recompute the HMAC over the header and payload segments using your secret. 4. Compare it to the signature segment using a constant-time comparison, not ==, to avoid leaking timing information. 5. Only after that check passes, also check exp (expiry) and nbf (not-before) against the current time - a validly-signed but expired token should still be rejected.

Every one of those steps matters; skipping the algorithm check specifically is the single most common real-world JWT vulnerability.

Mistakes that look like "it works"

Comparing signatures with a plain string === is usually fine in JavaScript since timing side-channels over HMAC comparisons are a fairly narrow attack surface for most applications, but well-audited JWT libraries use constant-time comparison anyway - copy that habit rather than re-deriving whether your case is safe.

Checking exp without checking the signature first is the classic mistake: an attacker who can forge arbitrary claims will simply set exp far in the future, so expiry checks only mean anything once you know the payload wasn't tampered with.

Algorithm confusion attacks

The best-known real-world JWT exploit swaps alg from RS256 (asymmetric - verified with a public key) to HS256 (symmetric - verified with a secret), then signs the forged token using the server's own public RSA key as if it were an HMAC secret. A library that blindly trusts the alg header and looks up "whatever key matches this algorithm" will verify the forged token successfully, because the public key is, by definition, public.

The fix is simple but easy to skip: your verification code should hardcode the expected algorithm for a given key, never read it from the untrusted token itself. If you only ever issue HS256 tokens, verify with HS256 only - don't let the token tell your verifier which algorithm to use.

FAQ

Can I verify an RS256 token with just a secret?
No. RS256 verification needs the issuer's public key, not a shared secret - that's the whole point of asymmetric signing. A tool that only supports HS256 can't verify RS256 tokens, by design.
Is a verified token automatically safe to trust?
A valid signature only proves the token wasn't altered and came from whoever holds the key. You still need to check expiry, issuer, and audience claims match what you expect.
Why did my token fail verification when the claims look right?
Any change to the header or payload bytes - including reformatting whitespace before re-encoding - produces a completely different signature. Verify the original token string, not a reconstructed one.

Browse all tools