How to Validate JWT Claims (exp, iss, aud) in Python

This code demonstrates how to decode and validate a JWT's essential claims—expiration (exp), issuer (iss), and audience (aud)—using the PyJWT library, returning clear error messages for common validation failures.

Easy Python 3.9+ Aug 9, 2026 API design & gRPC 12 views 0 copies

Requires third-party packages — install first
pip install PyJWT

Python code

42 lines
Python 3.9+
import jwt
from datetime import datetime, timezone, timedelta

SECRET = "mock-secret"

def validate_token(token, expected_iss, expected_aud):
    try:
        decoded = jwt.decode(
            token,
            SECRET,
            algorithms=["HS256"],
            options={"require": ["exp", "iss", "aud"]},
            audience=expected_aud,
            issuer=expected_iss
        )
        return {"valid": True, "claims": decoded}
    except jwt.ExpiredSignatureError:
        return {"valid": False, "error": "Token expired"}
    except jwt.InvalidAudienceError:
        return {"valid": False, "error": "Audience mismatch"}
    except jwt.InvalidIssuerError:
        return {"valid": False, "error": "Issuer mismatch"}
    except jwt.InvalidTokenError as e:
        return {"valid": False, "error": str(e)}

if __name__ == "__main__":
    now = datetime.now(timezone.utc)
    valid_token = jwt.encode(
        {"exp": now + timedelta(hours=1), "iss": "auth-service", "aud": "my-api", "user": "alice"},
        SECRET,
        algorithm="HS256"
    )
    expired_token = jwt.encode(
        {"exp": now - timedelta(minutes=5), "iss": "auth-service", "aud": "my-api", "user": "bob"},
        SECRET,
        algorithm="HS256"
    )

    print("Valid token:", validate_token(valid_token, "auth-service", "my-api"))
    print("Expired token:", validate_token(expired_token, "auth-service", "my-api"))
    print("Wrong issuer:", validate_token(valid_token, "wrong-issuer", "my-api"))
    print("Wrong audience:", validate_token(valid_token, "auth-service", "other-api"))

Output

stdout
Valid token: {'valid': True, 'claims': {'exp': 1712345678, 'iss': 'auth-service', 'aud': 'my-api', 'user': 'alice'}}
Expired token: {'valid': False, 'error': 'Token expired'}
Wrong issuer: {'valid': False, 'error': 'Issuer mismatch'}
Wrong audience: {'valid': False, 'error': 'Audience mismatch'}

How it works

The jwt.decode() function is the core of this validation. By passing options={'require': ['exp', 'iss', 'aud']}, you ensure these claims are present in the token. The issuer and audience parameters let you enforce exact matches for the issuer and audience. PyJWT raises specific exceptions for each validation failure—ExpiredSignatureError for expired tokens, InvalidAudienceError for audience mismatches, and InvalidIssuerError for issuer mismatches—which the function catches to return a readable error. The datetime.now(timezone.utc) ensures timezone-aware timestamps, which PyJWT requires for correct expiration comparison.

Common mistakes

  • Forgetting to install PyJWT with pip, leading to ModuleNotFoundError
  • Using naive datetime (without timezone) which PyJWT rejects
  • Not passing `options={'require': [...]}` so missing claims are ignored
  • Catching `InvalidTokenError` before specific exceptions, hiding details

Variations

  1. Manually check claims by decoding without verification and comparing exp, iss, aud yourself
  2. Use `jwt.decode` with a custom leeway parameter to allow small clock skew

Real-world use cases

  • Validating access tokens in a microservices API gateway before forwarding requests
  • Checking the issuer and audience of JWTs in a single sign-on (SSO) system
  • Ensuring tokens are not expired when processing them in a background job or batch process

Sponsored

Run locally

This sample needs third-party packages, so it cannot run in the browser IDE. Copy the code above, install the packages shown at the top, then run it in your own Python environment.

More from API design & gRPC

Related tutorials and quizzes for this topic.