JWT Security

header.payload.signature, each Base64Url. Header and payload are encoded, not encrypted, so the client reads them fine. All the security is in the signature. Break the signature check and you forge any claim.

alg values that matter: none (no signature), HS256 (HMAC, shared secret), RS256 (private key signs, public key verifies).

Tools

First thing on any token: decode the payload. Note alg, hunt for privilege claims (admin, role), and for leaked secrets.

Sensitive data in claims

Payload is public. If devs treat it like a server-side session you get password hashes, internal hostnames, keys. Just decode and read.

Signature not verified

Server never checks the signature. Test by stripping it: send header.payload. (trailing dot, empty third part). Still accepted means it is ignored, so flip admin to 1. Rare on public APIs, common server-to-server.

alg: none

Set header alg to none and drop the signature. If the server does not pin the algorithm, verification returns true. Try None/nOnE against naive filters. Cause: reading alg from the untrusted header straight into the verifier.

Weak HS secret

HS256 is only as strong as its secret.

# wordlist: github.com/wallarm/jwt-secrets
echo '<token>' > jwt.txt
hashcat -m 16500 -a 0 jwt.txt jwt.secrets.list

Crack it, then re-sign a tampered token with that secret.

Algorithm confusion (RS256 to HS256)

App signs RS256 and verifies with the public key, which you can usually get (JWKS endpoint, published, sometimes in the token). Change alg to HS256 and sign with HMAC using the public key as the secret. The server HMACs with that same public key and it matches, so you forged a token without the private key. Bites older/misconfigured libs that accept both algorithm families.

Missing / long exp

JWTs are not revocable by default. No exp (or a huge one) means a stolen or forged token lives forever. Libraries only check exp when present. Decode and look.

Cross-service relay (aud not enforced)

One issuer, many apps. The aud claim says which app a token is for, but each app has to enforce it. If app A skips the check, replay a token minted for app B (where you are admin) against app A and the admin claim gets honoured. Valid signature the whole way.

kid / header injection

kid tells the server which key to verify with. Unsafe use makes it a sink:

Checklist

Defence

Pin the accepted alg server-side, never trust the header. Reject none. Never mix symmetric and asymmetric in one verify path. Long random secret. No sensitive data in claims. Always set and check exp, enforce aud. Treat kid/jku/x5u as untrusted.