Issuing and verifying JWTs securely (RS256, claims, refresh rotation, revocation)

Backend Live Coding November 13, 2026 python

(1) A fixed list of algorithms, because the classic attack is alg=none or switching RS256 to HS256.

python
 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