(1) A fixed list of algorithms, because the classic attack is alg=none or switching RS256 to HS256.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | # ====================================================================== # Q: Write JWT issuing and verification. What do you watch out for? # # (1) A fixed list of algorithms, because the classic attack is alg=none or # switching RS256 to HS256. (2) Check exp, aud, iss. (3) A short lifetime for # the access token (5-15 minutes) and a refresh token with rotation: each # refresh issues a new one, and if an old one is used again, I invalidate the # whole family (a sign of theft). (4) I put no sensitive data in the JWT: it is # base64, not encrypted. (5) For logout before expiry: a denylist of the jti in # Redis with a TTL equal to the remaining lifetime. # ====================================================================== import jwt, uuid from datetime import datetime, timedelta, timezone ALGORITHM = 'RS256' # the private key signs, the public key verifies def issue_access_token(user): now = datetime.now(timezone.utc) payload = { 'sub': str(user.id), 'tid': str(user.tenant_id), 'roles': user.role_names, 'iat': now, 'exp': now + timedelta(minutes=15), # short lifetime 'iss': 'auth.example.com', 'aud': 'api.example.com', 'jti': str(uuid.uuid4()), # for revocation } return jwt.encode(payload, PRIVATE_KEY, algorithm=ALGORITHM) def verify(token): claims = jwt.decode(token, PUBLIC_KEY, algorithms=[ALGORITHM], # NEVER from the header audience='api.example.com', issuer='auth.example.com') # also checks exp if redis.exists(f'revoked:{claims["jti"]}'): raise jwt.InvalidTokenError('revoked') return claims |