Validate a JWT by fixing the allowed algorithms yourself, verifying every signature, checking iss, sub and aud against what you expect, distinguishing token types, and never trusting header values like kid, jku or x5u blindly. RFC 8725 lists these as best current practice.
The checklist
1. Fix the algorithms. Let the caller of your library choose allowed algorithms, and tie each key to one algorithm. Do not let the token header pick.
2. Use current algorithms. Avoid none unless other protections exist, and avoid RSA PKCS#1 v1.5 for encryption.
3. Validate every cryptographic step. If any signature or decryption check fails, reject the whole token. For nested tokens, that includes each layer.
4. Validate cryptographic inputs. With elliptic curve key agreement, confirm that supplied keys are valid points on the expected curve.
5. Use high entropy keys. Do not use a human memorable password directly as an HMAC key.
6. Do not compress before encrypting. Compressed size can leak information about the plaintext.
7. Use UTF-8. Encode and decode headers and claims only as UTF-8.
8. Check issuer and subject. Confirm the signing keys belong to the iss value and that sub is valid for your application.
9. Check audience. When one issuer serves many relying parties, require aud and reject tokens that do not name you.
10. Do not trust received claims. Sanitize kid before using it in a lookup, to prevent injection. Restrict jku and x5u to an allowlist of URLs to prevent server side request forgery.
11. Type your tokens. Use the typ header to label token kinds, preferably in a short form such as example+jwt.
12. Make validation rules mutually exclusive. If one issuer produces several kinds of JWT, make sure a token of one kind cannot pass as another, using typ, claims, keys, audiences or issuers.
Where this goes wrong in practice
The first item causes the most trouble. Libraries that take the algorithm from the header have historically allowed attackers to switch an asymmetric token to a symmetric one, using the public key as the HMAC secret, or to claim none. The fix is simple: pass an explicit algorithm list.
Items 9, 11 and 12 matter for token confusion. If your system issues ID tokens, access tokens and logout tokens from the same issuer, a token meant for one purpose should not be accepted for another.
A minimal validation function
def validate(token, key, issuer, audience):
claims = jwt_decode(
token,
key=key,
algorithms=["RS256"], # fixed, not from the header
issuer=issuer,
audience=audience,
options={"require": ["exp", "iss", "aud", "sub"]},
)
return claims
The function name jwt_decode stands for whatever maintained library you use. The point is the explicit algorithm list and required claims.
Test cases to keep
Keep a small fixed set of bad tokens in your test suite: unsigned, wrong algorithm, wrong issuer, wrong audience, expired, and one with a jku pointing at a test server. Each must fail.
Quick check
Is it safe to decode a JWT without verifying it?
Only for non security uses such as debugging. Never make an access decision from claims you have not verified.
Why check typ if the signature is valid?
A valid signature proves who issued the token, not what it is for. Typing stops one kind of token being replayed as another.
Sources
Last reviewed: 19 October 2026.