A Python library for generating and verifying JSON Web Tokens (JWTs) with support for multiple signature algorithms including RSA, ECDSA, HMAC, PSS, and EdDSA variants. Uses jwcrypto for cryptographic operations and provides comprehensive JWT validation capabilities.
Important Note: This package is deprecated and no longer maintained by the author. Using the library will trigger a deprecation warning at runtime.
pip install python_jwtjwcrypto>=1.4.2import python_jwt as jwt
import jwcrypto.jwk as jwk
from datetime import datetime, timedeltaimport python_jwt as jwt
import jwcrypto.jwk as jwk
from datetime import timedelta
# Generate RSA key pair
key = jwk.JWK.generate(kty='RSA', size=2048)
# Create payload with custom claims
payload = {'user': 'john', 'role': 'admin', 'permissions': ['read', 'write']}
# Generate JWT token with 5 minute expiration
token = jwt.generate_jwt(payload, key, 'PS256', timedelta(minutes=5))
# Verify the token
header, claims = jwt.verify_jwt(token, key, ['PS256'])
# Access claims
user_id = claims['user']
role = claims['role']Generate JSON Web Tokens with custom claims, automatic timestamps, and replay attack protection.
def generate_jwt(claims, priv_key=None, algorithm='PS512', lifetime=None,
expires=None, not_before=None, jti_size=16, other_headers=None):
"""
Generate a JSON Web Token with specified claims and signing parameters.
Args:
claims (dict): The claims to include in the token
priv_key (jwcrypto.jwk.JWK, optional): Private key for signing (None creates unsigned token)
algorithm (str): Signature algorithm (default: 'PS512')
lifetime (datetime.timedelta, optional): Token validity duration
expires (datetime.datetime, optional): Explicit expiration time
not_before (datetime.datetime, optional): Token valid-from time
jti_size (int): Size of unique token ID in bytes (default: 16, 0 to omit)
other_headers (dict, optional): Additional headers to include
Returns:
str: Unicode string containing the JWT
Raises:
ValueError: If other_headers redefines 'typ' or 'alg'
"""Supported Algorithms: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, ES256K, EdDSA, HS256, HS384, HS512, none
Automatic Claims Added:
lifetime or expires parameternot_before parameter or current timeBasic token generation:
# Simple token with automatic expiration
payload = {'sub': '1234567890', 'name': 'John Doe'}
token = jwt.generate_jwt(payload, key, 'RS256', timedelta(hours=1))Token with explicit times:
from datetime import datetime
# Token valid from tomorrow for 1 week
not_before = datetime.utcnow() + timedelta(days=1)
expires = not_before + timedelta(weeks=1)
token = jwt.generate_jwt(payload, key, 'PS384',
not_before=not_before, expires=expires)Unsigned token (for testing):
# Create unsigned token (algorithm forced to 'none')
token = jwt.generate_jwt(payload, None, 'PS256')Custom headers:
# Add custom headers
custom_headers = {'kid': 'key-id-123', 'cty': 'application/json'}
token = jwt.generate_jwt(payload, key, 'ES256', timedelta(minutes=30),
other_headers=custom_headers)Verify JWT signatures and validate time-based claims with comprehensive security checks.
def verify_jwt(jwt, pub_key=None, allowed_algs=None, iat_skew=timedelta(),
checks_optional=False, ignore_not_implemented=False):
"""
Verify a JSON Web Token's signature and validate claims.
Args:
jwt (str): The JWT to verify
pub_key (jwcrypto.jwk.JWK, optional): Public key for verification
allowed_algs (list, optional): List of allowed signature algorithms (None defaults to empty list)
iat_skew (datetime.timedelta): Clock skew tolerance for 'iat' claim
checks_optional (bool): Whether typ/iat/nbf/exp claims are optional
ignore_not_implemented (bool): Whether to ignore unsupported header properties
Returns:
tuple: (header, claims) containing parsed header and claims dictionaries
Raises:
Exception: Various exceptions if verification fails, including:
- Invalid JWT format
- Algorithm header not present or not in allowed list
- Unknown or unsupported header properties
- No public key provided but 'none' algorithm not allowed
- Invalid 'typ' header (must be 'JWT')
- Missing required claims (iat, nbf, exp) when checks_optional=False
- Token issued in future (iat validation)
- Token not yet valid (nbf validation)
- Token expired (exp validation)
- Signature verification failures
"""Validation Checks Performed:
Basic verification:
# Verify with specific algorithm
header, claims = jwt.verify_jwt(token, pub_key, ['RS256'])Multiple allowed algorithms:
# Allow multiple signature algorithms
header, claims = jwt.verify_jwt(token, pub_key, ['RS256', 'PS256', 'ES256'])Clock skew tolerance:
# Allow 30 seconds clock skew for distributed systems
header, claims = jwt.verify_jwt(token, pub_key, ['PS256'],
iat_skew=timedelta(seconds=30))Relaxed validation:
# Make time-based claims optional
header, claims = jwt.verify_jwt(token, pub_key, ['HS256'],
checks_optional=True)Extract header and claims from JWT without signature verification for inspection purposes.
def process_jwt(jwt):
"""
Process a JWT without verifying it to extract header and claims.
Args:
jwt (str): The JWT to process
Returns:
tuple: (header, claims) containing parsed header and claims dictionaries
Raises:
Exception: If JWT format is invalid (must match pattern: header.payload.signature)
"""Use this before verify_jwt when you need to examine token contents to determine verification parameters (e.g., selecting the appropriate public key based on issuer).
Pre-verification inspection:
# Examine token before verification
header, claims = jwt.process_jwt(token)
# Select appropriate key based on issuer
issuer = claims.get('iss')
if issuer == 'auth.example.com':
pub_key = load_example_key()
elif issuer == 'auth.other.com':
pub_key = load_other_key()
# Now verify with the correct key
verified_header, verified_claims = jwt.verify_jwt(token, pub_key, ['RS256'])Algorithm inspection:
# Check algorithm before verification
header, claims = jwt.process_jwt(token)
algorithm = header.get('alg')
# Use algorithm-specific key
if algorithm.startswith('RS'):
pub_key = rsa_public_key
elif algorithm.startswith('ES'):
pub_key = ecdsa_public_key
verified_header, verified_claims = jwt.verify_jwt(token, pub_key, [algorithm])Work with PEM-format keys for interoperability with other systems.
# Generate and export keys
key = jwk.JWK.generate(kty='RSA', size=2048)
priv_pem = key.export_to_pem(private_key=True, password=None)
pub_pem = key.export_to_pem()
# Import keys from PEM
priv_key = jwk.JWK.from_pem(priv_pem)
pub_key = jwk.JWK.from_pem(pub_pem)
# Use with JWT functions
token = jwt.generate_jwt(payload, priv_key, 'RS256', timedelta(hours=1))
header, claims = jwt.verify_jwt(token, pub_key, ['RS256'])Different key types support different signature algorithms:
# RSA keys (RS256, RS384, RS512, PS256, PS384, PS512)
rsa_key = jwk.JWK.generate(kty='RSA', size=2048)
# ECDSA keys (ES256, ES384, ES512, ES256K)
ec_key = jwk.JWK.generate(kty='EC', curve='P-256') # For ES256
ec_key = jwk.JWK.generate(kty='EC', curve='P-384') # For ES384
ec_key = jwk.JWK.generate(kty='EC', curve='P-521') # For ES512
# EdDSA keys (EdDSA)
ed_key = jwk.JWK.generate(kty='OKP', curve='Ed25519')
# HMAC keys (HS256, HS384, HS512)
hmac_key = jwk.JWK.generate(kty='oct', size=256)The library prevents algorithm confusion attacks by requiring explicit specification of allowed algorithms:
# Secure: explicitly allow only specific algorithms
header, claims = jwt.verify_jwt(token, pub_key, ['PS256'])
# Insecure: never pass None or empty list for allowed_algs
# This would allow any algorithm, including 'none'Generate unique token IDs (JTI) to detect and prevent replay attacks:
# Default: 16-byte (128-bit) random JTI
token = jwt.generate_jwt(payload, key, 'RS256', timedelta(hours=1))
# Custom JTI size
token = jwt.generate_jwt(payload, key, 'RS256', timedelta(hours=1), jti_size=32)
# Omit JTI (not recommended for security)
token = jwt.generate_jwt(payload, key, 'RS256', timedelta(hours=1), jti_size=0)Comprehensive time-based claim validation with configurable clock skew:
# Strict time validation (default)
header, claims = jwt.verify_jwt(token, pub_key, ['RS256'])
# Allow clock skew for distributed systems
header, claims = jwt.verify_jwt(token, pub_key, ['RS256'],
iat_skew=timedelta(minutes=1))The library may raise various exceptions during verification. Always wrap JWT operations in try-catch blocks:
try:
header, claims = jwt.verify_jwt(token, pub_key, ['RS256'])
# Token is valid
user_id = claims['sub']
except Exception as e:
# Token verification failed
print(f"JWT verification failed: {e}")
# Handle invalid token (redirect to login, etc.)import python_jwt as jwt
import jwcrypto.jwk as jwk
from datetime import datetime, timedelta
# Generate RSA key pair
key = jwk.JWK.generate(kty='RSA', size=2048)
# Create comprehensive payload
payload = {
'sub': '1234567890', # Subject (user ID)
'name': 'John Doe', # User name
'iss': 'auth.example.com', # Issuer
'aud': 'api.example.com', # Audience
'roles': ['admin', 'user'], # Custom claim
'permissions': ['read', 'write', 'delete']
}
try:
# Generate token with 1 hour expiration
token = jwt.generate_jwt(
payload,
key,
'PS256', # RSA-PSS with SHA-256
timedelta(hours=1),
jti_size=16 # 128-bit unique token ID
)
print(f"Generated token: {token[:50]}...")
# Verify the token
header, claims = jwt.verify_jwt(
token,
key,
['PS256'], # Only allow PS256 algorithm
iat_skew=timedelta(seconds=30) # 30-second clock skew tolerance
)
print("Token verification successful!")
print(f"Subject: {claims['sub']}")
print(f"Issued at: {datetime.utcfromtimestamp(claims['iat'])}")
print(f"Expires at: {datetime.utcfromtimestamp(claims['exp'])}")
print(f"Roles: {claims['roles']}")
except ValueError as e:
print(f"Token generation failed: {e}")
except Exception as e:
print(f"Token verification failed: {e}")The library is designed for interoperability with other JWT implementations and includes test coverage for compatibility with the jose JavaScript library. Tokens generated by python-jwt can be verified by other JWT libraries and vice versa, as long as they follow the JWT specification.