Learn
What is a JWT?
A short introduction to JSON Web Tokens, how they're signed and encrypted, and the mistakes that make them unsafe.
A token you can check without a database
A JSON Web Token (JWT, said “jot”) is a compact way to send a set of facts, called claims, that the receiver can check without asking anyone. A sign-in server issues one after you log in, and every API you call can confirm it's genuine using only a key.
Most JWTs are signed, not encrypted. Anyone holding one can read it, so it should never carry secrets. The signature only proves who wrote it and that nobody changed it. The standard is RFC 7519.
Three parts, separated by dots
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQyIiwiZXhwIjoxNzk0MzQ1MTk4fQ.eWs5MFtZNmQSIeMI2pP2pK6NWfiuR6MfAd2wC6WeFDE
Header: how the token is protected, for example {"alg": "RS256", "kid": "key-2"}. alg names the algorithm and kid says which of the issuer's keys was used.
Payload: the claims, such as who the token is about and when it expires.
Signature: computed over the first two parts with the issuer's key. Change one character of the header or payload and it no longer matches.
The first two parts are just JSON in Base64URL encoding, which is why any tool can decode them. Decoding isn't verifying: only checking the signature with the right key tells you the token is genuine.
Signed (JWS) and encrypted (JWE)
A signed token is a JWS: three parts, readable by anyone. An encrypted token is a JWE: five parts (header, encrypted key, IV, ciphertext and tag), readable only with the right private or shared key. A JWE often wraps a signed token, so the contents are both private and verifiable.
Registered claims
These have the same meaning everywhere. Issuers add their own claims alongside them.
- Issuer
iss - Who created and signed the token, usually the sign-in server's URL. Use it to find the right keys, never as proof on its own.
- Subject
sub - Who or what the token is about: a user or a service ID, unique within the issuer.
- Audience
aud - Who the token is for. A string or an array. A service should reject any token that doesn't name it.
- Expires
exp - Seconds since 1970 after which the token must be rejected.
- Not before
nbf - Seconds since 1970 before which the token must be rejected.
- Issued at
iat - When the token was created. Useful for spotting tokens that live too long.
- Token ID
jti - A unique ID for this token, so a service can refuse to accept the same one twice.
Time claims are whole seconds since 1 January 1970 UTC, not milliseconds.
Signing algorithms
HMAC (HS256, HS384, HS512) uses one shared secret. Whoever can verify a token can also create one, so it suits a single service talking to itself.
Public-key (RS*, PS*, ES*, EdDSA) signs with a private key that only the issuer holds and verifies with a public key that anyone can have. This is what sign-in providers use, because APIs can check tokens without being able to forge them.
Where the keys come from
OpenID Connect providers publish their public keys as a JSON Web Key Set (JWKS). Take the token's iss, add /.well-known/openid-configuration, and the document there gives a jwks_uri. The key whose kid matches the token's header verifies it.
A match proves that issuer signed the token. It doesn't prove you should trust that issuer: a service should only accept issuers it already knows. Tessera does this lookup with Fetch keys from issuer.
Common mistakes
Tessera's security checks look for each of these.
Unsigned tokens (alg: none)
A token whose header says "alg": "none" has no signature, so anyone can write one. Libraries that honour none by default have let attackers sign themselves in. Never accept it outside tests.
Missing or long expiry
Without exp, a leaked token works forever. Access tokens usually last minutes to an hour. Check nbf and iat too: values in the future often mean a clock is wrong, and values in milliseconds instead of seconds are a common bug.
Missing or unchecked audience
If a token has no aud, or the service doesn't check it, a token issued for one API can be replayed against another that trusts the same issuer. Each service should accept only tokens that name it. When aud is an array, it's enough for one entry to match.
Weak or published HMAC secrets
HS256, HS384 and HS512 use one shared secret to sign and verify. A short secret can be guessed offline from a single token, and secrets copied from tutorials are in every wordlist. Use at least as many random bytes as the hash size (32 for HS256).
Algorithm confusion
If a verifier lets the token pick its algorithm, an attacker can take an RSA public key, which is public, and use it as an HMAC secret. Always decide the algorithm from the key you hold, not from the token's header. Tessera only uses a key with the algorithm it was made for.
Keys and key URLs in the header
The jku, x5u, jwk and x5c header parameters let a token point to, or carry, the key that verifies it. Trusting them blindly means trusting the attacker's key. Only use them when the key or certificate chain leads back to something you already trust.
Weak JWE key management
RSA1_5 is open to padding-oracle attacks, so prefer RSA-OAEP-256 or ECDH-ES. PBES2 derives a key from a password, and a low iteration count (p2c) makes that password quick to guess.
Ready to look at a real token? The Guide shows how to inspect, verify, decrypt and build tokens in Tessera.