PyJWT

raw JSON →
2.12.1 verified Tue May 12 auth: no python install: verified quickstart: verified

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.

pip install pyjwt
error jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
cause The `algorithms` parameter was either not provided or did not include the algorithm used to sign the token when calling `jwt.decode()`. PyJWT requires explicit declaration of allowed algorithms for security reasons.
fix
Pass a list of explicitly allowed algorithms, matching the token's 'alg' header, to the algorithms parameter in jwt.decode(). Example: jwt.decode(token, secret, algorithms=['HS256'])
error NotImplementedError: Algorithm 'RS256' could not be found. Do you have cryptography installed?
cause You are attempting to use an asymmetric algorithm (such as RS*, ES*, PS*, or EdDSA) but the optional `cryptography` dependency for PyJWT is not installed.
fix
Install PyJWT with the crypto extra to include the cryptography dependency: pip install "pyjwt[crypto]" (note the quotes for shell compatibility).
error jwt.exceptions.InvalidSignatureError: Signature verification failed
cause The secret key or public key provided to `jwt.decode()` does not match the key used to sign the token, or the token itself is malformed or has been tampered with.
fix
Ensure the key parameter passed to jwt.decode() is the correct secret (for symmetric algorithms like HS256) or the correct public key (for asymmetric algorithms like RS256) that was used during the token's encoding.
error AttributeError: 'module' object has no attribute 'encode'
cause This error often occurs due to a naming conflict when both the `PyJWT` library (which provides the `jwt` module) and an older, incompatible `jwt` package are installed simultaneously.
fix
Uninstall both packages (pip uninstall jwt pyjwt) to remove any conflicting installations, then reinstall only PyJWT (pip install pyjwt).
breaking jwt.encode() returns str in 2.x, not bytes. Calling .decode('utf-8') on the result raises AttributeError.
fix Remove any .decode('utf-8') calls on the encode() return value. It is already a str in 2.0+.
breaking Exception aliases ExpiredSignature, InvalidAudience, and InvalidIssuer were removed in 2.0. Code catching these names will silently fail to catch the exception.
fix Catch jwt.ExpiredSignatureError, jwt.InvalidAudienceError, and jwt.InvalidIssuerError respectively.
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.
fix Use options={"verify_signature": False} to skip verification, e.g. jwt.decode(token, options={"verify_signature": False}).
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.
fix Upgrade to pyjwt>=2.12.0 immediately.
gotcha algorithms= parameter is required in jwt.decode(). Omitting it accepts any algorithm, enabling alg:none and key-confusion attacks (CVE-2022-29217).
fix Always pass a hard-coded allowlist: jwt.decode(token, key, algorithms=["HS256"]). Never derive the list from the token header.
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.
fix Pass options={"require": ["exp", "iat", "sub"]} to enforce presence and validation of critical claims.
gotcha RSA/EC/PS*/EdDSA algorithms silently fail with an unhelpful ImportError or InvalidAlgorithmError if the cryptography package is not installed.
fix Install pyjwt[cryptography] when using any non-HMAC algorithm.
pip install pyjwt[cryptography]
python os / libc variant status wheel install import disk
3.10 alpine (musl) pyjwt - - 0.12s 18.3M
3.10 alpine (musl) cryptography - - 0.12s 18.3M
3.10 slim (glibc) pyjwt - - 0.09s 19M
3.10 slim (glibc) cryptography - - 0.08s 19M
3.11 alpine (musl) pyjwt - - 0.15s 19.9M
3.11 alpine (musl) cryptography - - 0.15s 19.9M
3.11 slim (glibc) pyjwt - - 0.17s 20M
3.11 slim (glibc) cryptography - - 0.13s 20M
3.12 alpine (musl) pyjwt - - 0.12s 11.8M
3.12 alpine (musl) cryptography - - 0.12s 11.8M
3.12 slim (glibc) pyjwt - - 0.13s 12M
3.12 slim (glibc) cryptography - - 0.17s 12M
3.13 alpine (musl) pyjwt - - 0.11s 11.4M
3.13 alpine (musl) cryptography - - 0.11s 11.4M
3.13 slim (glibc) pyjwt - - 0.11s 12M
3.13 slim (glibc) cryptography - - 0.11s 12M
3.9 alpine (musl) pyjwt - - 0.12s 17.8M
3.9 alpine (musl) cryptography - - 0.12s 17.8M
3.9 slim (glibc) pyjwt - - 0.10s 18M
3.9 slim (glibc) cryptography - - 0.10s 18M

Encode and decode a signed JWT with HS256, validating exp/aud/iss claims. token is a str in PyJWT 2.x.

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)