Someone forwards a coupon that looks like it came from your store — same name, same styling. It’s fake. A JWT has the exact same problem: what makes it real is not what it says, but who could produce its signature.
A JWT is three parts
header.payload.signature
The signature is computed over header + payload. Change a single byte and it no longer matches — that’s the whole point.
Keys are a pair
- Private key — used to sign. There is exactly one, and it never leaves the issuer.
- Public key — used to verify. It’s published, so anyone can check a signature.
So: only the issuer can produce a signature, but everyone can verify one. The public key can verify — it cannot forge.
Publishing keys: JWKS + kid
Verifiers fetch the public keys from a JWKS endpoint (/.well-known/jwks.json). Each key carries a kid (key id), and each issued token declares which kid signed it. That’s how a verifier picks the right public key.
Rotation: overlap or you break everything
When you rotate keys, publish the new public key first and keep the old one alongside until every token signed by the old key has expired. Pull the old key too early and every live token becomes invalid at once.
Verify with a fixed algorithm — never trust the token
Only accept signatures from an explicit algorithm allowlist (e.g. RS256/ES256). Never trust the token’s own alg header — this is the classic alg=none and algorithm-confusion footgun. RFC 8725 (JWT Best Current Practices) is explicit about it.
A real case: Storm-0558 (2023)
In 2023, a signing key that should have been scoped to one system was obtained and used to forge tokens, letting attackers read email across 20+ organizations. A single leaked signing key is a skeleton key for everything that trusts it.
Engineering practice
- Generate keys as a pair (RSA-2048 / EC). The private key never leaves the service.
- Publish JWKS with a
kidon each key; include thekidin every token. - Rotate with overlap: new + old public keys published together until old tokens expire.
-
Fix the algorithm whitelist; reject
none; don’t let the token tell you how to verify it.
These practices come from building Autional, an open-core identity layer — https://www.autional.com
Reference: RFC 7515 (JWS), RFC 7517 (JWK/JWKS), RFC 7518 (JWA), RFC 7519 (JWT), RFC 8725 (JWT BCP); Storm-0558 (Microsoft disclosure 2023 / CISA CSRB report 2024).
Top comments (0)