PyJWT
PyJWT is the canonical Python implementation of JSON Web Tokens (RFC 7519), supporting HMAC (HS256/384/512), RSA (RS256/384/512, PS256/384/512), EC (ES256/384/512), and OKP (EdDSA) algorithms. Current stable version is 2.12.1 (released March 2026). The project follows an irregular release cadence driven by security advisories and feature PRs, with several releases per year. Asymmetric algorithms (RS*, ES*, PS*, EdDSA) require the optional `cryptography` extra.
Warnings
- breaking jwt.encode() returns str in 2.x, not bytes. Calling .decode('utf-8') on the result raises AttributeError.
- breaking Exception aliases ExpiredSignature, InvalidAudience, and InvalidIssuer were removed in 2.0. Code catching these names will silently fail to catch the exception.
- breaking jwt.decode(token, key, verify=False) does nothing in 2.x and raises a deprecation warning. Passing extra kwargs to decode() is flagged RemovedInPyjwt3Warning.
- breaking CVE-2026-32597 (GHSA-752w-5fwx-jx9f, CVSS 7.5 HIGH): versions < 2.12.0 silently accept JWS tokens with unknown crit header extensions, violating RFC 7515 §4.1.11.
- gotcha algorithms= parameter is required in jwt.decode(). Omitting it accepts any algorithm, enabling alg:none and key-confusion attacks (CVE-2022-29217).
- gotcha Optional claims (exp, iat, nbf, sub, jti) are only validated when present. A token missing exp is accepted even if you call decode() without options.
- gotcha RSA/EC/PS*/EdDSA algorithms silently fail with an unhelpful ImportError or InvalidAlgorithmError if the cryptography package is not installed.
Install
-
pip install pyjwt -
pip install pyjwt[cryptography]
Imports
- jwt (module)
import jwt
- PyJWKClient
from jwt import PyJWKClient
- PyJWK
from jwt import PyJWK
- ExpiredSignatureError
from jwt.exceptions import ExpiredSignatureError
- InvalidAudienceError
from jwt.exceptions import InvalidAudienceError
- PyJWKClientConnectionError
from jwt import PyJWKClientConnectionError
Quickstart
import os
import jwt
from datetime import datetime, timezone, timedelta
SECRET = os.environ.get('JWT_SECRET', 'change-me-use-32-plus-chars-prod!')
# Encode
payload = {
"sub": "user-123",
"iss": "my-service",
"aud": "my-api",
"exp": datetime.now(tz=timezone.utc) + timedelta(hours=1),
"iat": datetime.now(tz=timezone.utc),
}
token: str = jwt.encode(payload, SECRET, algorithm="HS256")
print("token type:", type(token)) # <class 'str'> in 2.x
# Decode — always pass algorithms= to prevent alg confusion attacks
try:
decoded = jwt.decode(
token,
SECRET,
algorithms=["HS256"], # required; never omit
audience="my-api", # validates 'aud' claim
issuer="my-service", # validates 'iss' claim
options={"require": ["exp", "iat", "sub"]},
)
print(decoded)
except jwt.ExpiredSignatureError:
print("token expired")
except jwt.InvalidTokenError as e:
print("invalid token:", e)