Welcome to PyJWT
¶
PyJWT
is a Python library which allows you to encode and decode JSON Web
Tokens (JWT). JWT is an open, industry-standard (RFC 7519) for representing
claims securely between two parties.
Sponsor¶
![]() |
If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0’s Python SDK and free plan at auth0.com/developers. |
Installation¶
You can install pyjwt
with pip
:
$ pip install pyjwt
See Installation for more information.
Example Usage¶
>>> import jwt
>>> encoded_jwt = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
>>> print(encoded_jwt)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
>>> jwt.decode(encoded_jwt, "secret", algorithms=["HS256"])
{'some': 'payload'}
See Usage Examples for more examples.
Index¶
Installation¶
You can install PyJWT
with pip
:
$ pip install pyjwt
Cryptographic Dependencies (Optional)¶
If you are planning on encoding or decoding tokens using certain digital
signature algorithms (like RSA or ECDSA), you will need to install the
cryptography library. This can be installed explicitly, or as a required
extra in the pyjwt
requirement:
$ pip install pyjwt[crypto]
The pyjwt[crypto]
format is recommended in requirements files in
projects using PyJWT
, as a separate cryptography
requirement line
may later be mistaken for an unused requirement and removed.
Usage Examples¶
Encoding & Decoding Tokens with HS256¶
>>> import jwt
>>> key = "secret"
>>> encoded = jwt.encode({"some": "payload"}, key, algorithm="HS256")
>>> print(encoded)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
>>> jwt.decode(encoded, key, algorithms="HS256")
{'some': 'payload'}
Encoding & Decoding Tokens with RS256 (RSA)¶
RSA encoding and decoding require the cryptography
module. See Cryptographic Dependencies (Optional).
>>> import jwt
>>> private_key = b"-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBS..."
>>> public_key = b"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEAC..."
>>> encoded = jwt.encode({"some": "payload"}, private_key, algorithm="RS256")
>>> print(encoded)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
>>> decoded = jwt.decode(encoded, public_key, algorithms=["RS256"])
{'some': 'payload'}
If your private key needs a passphrase, you need to pass in a PrivateKey
object from cryptography
.
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
pem_bytes = b"-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBS..."
passphrase = b"your password"
private_key = serialization.load_pem_private_key(
pem_bytes, password=passphrase, backend=default_backend()
)
encoded = jwt.encode({"some": "payload"}, private_key, algorithm="RS256")
If you are repeatedly encoding with the same private key, reusing the same
RSAPrivateKey
also has performance benefits because it avoids the
CPU-intensive RSA_check_key
primality test.
Specifying Additional Headers¶
>>> jwt.encode(
... {"some": "payload"},
... "secret",
... algorithm="HS256",
... headers={"kid": "230498151c214b788dd97f22b85410a5"},
... )
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1In0.eyJzb21lIjoicGF5bG9hZCJ9.DogbDGmMHgA_bU05TAB-R6geQ2nMU2BRM-LnYEtefwg'
Reading the Claimset without Validation¶
If you wish to read the claimset of a JWT without performing validation of the
signature or any of the registered claim names, you can set the
verify_signature
option to False
.
Note: It is generally ill-advised to use this functionality unless you clearly understand what you are doing. Without digital signature information, the integrity or authenticity of the claimset cannot be trusted.
>>> jwt.decode(encoded, options={"verify_signature": False})
{'some': 'payload'}
Reading Headers without Validation¶
Some APIs require you to read a JWT header without validation. For example, in situations where the token issuer uses multiple keys and you have no way of knowing in advance which one of the issuer’s public keys or shared secrets to use for validation, the issuer may include an identifier for the key in the header.
>>> jwt.get_unverified_header(encoded)
{'alg': 'RS256', 'typ': 'JWT', 'kid': 'key-id-12345...'}
Registered Claim Names¶
The JWT specification defines some registered claim names and defines how they should be used. PyJWT supports these registered claim names:
- “exp” (Expiration Time) Claim
- “nbf” (Not Before Time) Claim
- “iss” (Issuer) Claim
- “aud” (Audience) Claim
- “iat” (Issued At) Claim
Expiration Time Claim (exp)¶
The “exp” (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the “exp” claim requires that the current date/time MUST be before the expiration date/time listed in the “exp” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
You can pass the expiration time as a UTC UNIX timestamp (an int) or as a datetime, which will be converted into an int. For example:
jwt.encode({"exp": 1371720939}, "secret")
jwt.encode({"exp": datetime.now(tz=timezone.utc)}, "secret")
Expiration time is automatically verified in jwt.decode() and raises jwt.ExpiredSignatureError if the expiration time is in the past:
try:
jwt.decode("JWT_STRING", "secret", algorithms=["HS256"])
except jwt.ExpiredSignatureError:
# Signature has expired
...
Expiration time will be compared to the current UTC time (as given by timegm(datetime.now(tz=timezone.utc).utctimetuple())), so be sure to use a UTC timestamp or datetime in encoding.
You can turn off expiration time verification with the verify_exp parameter in the options argument.
PyJWT also supports the leeway part of the expiration time definition, which means you can validate a expiration time which is in the past but not very far. For example, if you have a JWT payload with a expiration time set to 30 seconds after creation but you know that sometimes you will process it after 30 seconds, you can set a leeway of 10 seconds in order to have some margin:
jwt_payload = jwt.encode(
{"exp": datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(seconds=30)},
"secret",
)
time.sleep(32)
# JWT payload is now expired
# But with some leeway, it will still validate
jwt.decode(jwt_payload, "secret", leeway=10, algorithms=["HS256"])
Instead of specifying the leeway as a number of seconds, a datetime.timedelta instance can be used. The last line in the example above is equivalent to:
jwt.decode(
jwt_payload, "secret", leeway=datetime.timedelta(seconds=10), algorithms=["HS256"]
)
Not Before Time Claim (nbf)¶
The “nbf” (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the “nbf” claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the “nbf” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
The nbf claim works similarly to the exp claim above.
jwt.encode({"nbf": 1371720939}, "secret")
jwt.encode({"nbf": datetime.now(tz=timezone.utc)}, "secret")
Issuer Claim (iss)¶
The “iss” (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The “iss” value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.
payload = {"some": "payload", "iss": "urn:foo"}
token = jwt.encode(payload, "secret")
decoded = jwt.decode(token, "secret", issuer="urn:foo", algorithms=["HS256"])
If the issuer claim is incorrect, jwt.InvalidIssuerError will be raised.
Audience Claim (aud)¶
The “aud” (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the “aud” claim when this claim is present, then the JWT MUST be rejected.
In the general case, the “aud” value is an array of case- sensitive strings, each containing a StringOrURI value.
payload = {"some": "payload", "aud": ["urn:foo", "urn:bar"]}
token = jwt.encode(payload, "secret")
decoded = jwt.decode(token, "secret", audience="urn:foo", algorithms=["HS256"])
In the special case when the JWT has one audience, the “aud” value MAY be a single case-sensitive string containing a StringOrURI value.
payload = {"some": "payload", "aud": "urn:foo"}
token = jwt.encode(payload, "secret")
decoded = jwt.decode(token, "secret", audience="urn:foo", algorithms=["HS256"])
If multiple audiences are accepted, the audience
parameter for
jwt.decode
can also be an iterable
payload = {"some": "payload", "aud": "urn:foo"}
token = jwt.encode(payload, "secret")
decoded = jwt.decode(
token, "secret", audience=["urn:foo", "urn:bar"], algorithms=["HS256"]
)
The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.
If the audience claim is incorrect, jwt.InvalidAudienceError will be raised.
Issued At Claim (iat)¶
The iat (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.
If the iat claim is not a number, an jwt.InvalidIssuedAtError exception will be raised.
jwt.encode({"iat": 1371720939}, "secret")
jwt.encode({"iat": datetime.now(tz=timezone.utc)}, "secret")
Requiring Presence of Claims¶
If you wish to require one or more claims to be present in the claimset, you can set the require
parameter to include these claims.
>>> jwt.decode(encoded, options={"require": ["exp", "iss", "sub"]})
{'exp': 1371720939, 'iss': 'urn:foo', 'sub': '25c37522-f148-4cbf-8ee6-c4a9718dd0af'}
Retrieve RSA signing keys from a JWKS endpoint¶
>>> import jwt
>>> from jwt import PyJWKClient
>>> token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA"
>>> kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw"
>>> url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
>>> jwks_client = PyJWKClient(url)
>>> signing_key = jwks_client.get_signing_key_from_jwt(token)
>>> data = jwt.decode(
... token,
... signing_key.key,
... algorithms=["RS256"],
... audience="https://expenses-api",
... options={"verify_exp": False},
... )
>>> print(data)
{'iss': 'https://dev-87evx9ru.auth0.com/', 'sub': 'aW4Cca79xReLWUz0aE2H6kD0O3cXBVtC@clients', 'aud': 'https://expenses-api', 'iat': 1572006954, 'exp': 1572006964, 'azp': 'aW4Cca79xReLWUz0aE2H6kD0O3cXBVtC', 'gty': 'client-credentials'}
Frequently Asked Questions¶
How can I extract a public / private key from a x509 certificate?¶
The load_pem_x509_certificate()
function from cryptography
can be used to
extract the public or private keys from a x509 certificate in PEM format.
from cryptography.x509 import load_pem_x509_certificate
cert_str = b"-----BEGIN CERTIFICATE-----MIIDETCCAfm..."
cert_obj = load_pem_x509_certificate(cert_str)
public_key = cert_obj.public_key()
private_key = cert_obj.private_key()
Digital Signature Algorithms¶
The JWT specification supports several algorithms for cryptographic signing. This library currently supports:
- HS256 - HMAC using SHA-256 hash algorithm (default)
- HS384 - HMAC using SHA-384 hash algorithm
- HS512 - HMAC using SHA-512 hash algorithm
- ES256 - ECDSA signature algorithm using SHA-256 hash algorithm
- ES256K - ECDSA signature algorithm with secp256k1 curve using SHA-256 hash algorithm
- ES384 - ECDSA signature algorithm using SHA-384 hash algorithm
- ES512 - ECDSA signature algorithm using SHA-512 hash algorithm
- RS256 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-256 hash algorithm
- RS384 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-384 hash algorithm
- RS512 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-512 hash algorithm
- PS256 - RSASSA-PSS signature using SHA-256 and MGF1 padding with SHA-256
- PS384 - RSASSA-PSS signature using SHA-384 and MGF1 padding with SHA-384
- PS512 - RSASSA-PSS signature using SHA-512 and MGF1 padding with SHA-512
- EdDSA - Both Ed25519 signature using SHA-512 and Ed448 signature using SHA-3 are supported. Ed25519 and Ed448 provide 128-bit and 224-bit security respectively.
Asymmetric (Public-key) Algorithms¶
Usage of RSA (RS*) and EC (EC*) algorithms require a basic understanding of how public-key cryptography is used with regards to digital signatures. If you are unfamiliar, you may want to read this article.
When using the RSASSA-PKCS1-v1_5 algorithms, the key argument in both
jwt.encode()
and jwt.decode()
("secret"
in the examples) is expected to
be either an RSA public or private key in PEM or SSH format. The type of key
(private or public) depends on whether you are signing or verifying a token.
When using the ECDSA algorithms, the key
argument is expected to
be an Elliptic Curve public or private key in PEM format. The type of key
(private or public) depends on whether you are signing or verifying.
Specifying an Algorithm¶
You can specify which algorithm you would like to use to sign the JWT by using the algorithm parameter:
>>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS512")
>>> print(encoded)
eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.WTzLzFO079PduJiFIyzrOah54YaM8qoxH9fLMQoQhKtw3_fMGjImIOokijDkXVbyfBqhMo2GCNu4w9v7UXvnpA
When decoding, you can also specify which algorithms you would like to permit when validating the JWT by using the algorithms parameter which takes a list of allowed algorithms:
>>> jwt.decode(encoded, "secret", algorithms=["HS512", "HS256"])
{'some': 'payload'}
In the above case, if the JWT has any value for its alg header other than
HS512 or HS256, the claim will be rejected with an InvalidAlgorithmError
.
Warning
Do not compute the algorithms
parameter based on the
alg
from the token itself, or on any other data that an
attacker may be able to influence, as that might expose you to
various vulnerabilities (see RFC 8725 §2.1). Instead,
either hard-code a fixed value for algorithms
, or configure it
in the same place you configure the key
. Make sure not to mix
symmetric and asymmetric algorithms that interpret the key
in
different ways (e.g. HS* and RS*).
API Reference¶
-
jwt.
encode
(payload, key, algorithm="HS256", headers=None, json_encoder=None)¶ Encode the
payload
as JSON Web Token.Parameters: - payload (dict) – JWT claims, e.g.
dict(iss=..., aud=..., sub=...)
- key (str) –
a key suitable for the chosen algorithm:
- for asymmetric algorithms: PEM-formatted private key, a multiline string
- for symmetric algorithms: plain string, sufficiently long for security
- algorithm (str) – algorithm to sign the token with, e.g.
"ES256"
. Ifheaders
includesalg
, it will be preferred to this parameter. - headers (dict) – additional JWT header fields, e.g.
dict(kid="my-key-id")
. - json_encoder (json.JSONEncoder) – custom JSON encoder for
payload
andheaders
Return type: Returns: a JSON Web Token
- payload (dict) – JWT claims, e.g.
-
jwt.
decode
(jwt, key="", algorithms=None, options=None, audience=None, issuer=None, leeway=0)¶ Verify the
jwt
token signature and return the token claims.Parameters: - jwt (str) – the token to be decoded
- key (str) – the key suitable for the allowed algorithm
- algorithms (list) –
allowed algorithms, e.g.
["ES256"]
Warning
Do not compute the
algorithms
parameter based on thealg
from the token itself, or on any other data that an attacker may be able to influence, as that might expose you to various vulnerabilities (see RFC 8725 §2.1). Instead, either hard-code a fixed value foralgorithms
, or configure it in the same place you configure thekey
. Make sure not to mix symmetric and asymmetric algorithms that interpret thekey
in different ways (e.g. HS* and RS*). - options (dict) –
extended decoding and validation options
verify_signature=True
verify the JWT cryptographic signaturerequire=[]
list of claims that must be present. Example:require=["exp", "iat", "nbf"]
. Only verifies that the claims exists. Does not verify that the claims are valid.verify_aud=verify_signature
check thataud
(audience) claim matchesaudience
verify_iss=verify_signature
check thatiss
(issuer) claim matchesissuer
verify_exp=verify_signature
check thatexp
(expiration) claim value is in the futureverify_iat=verify_signature
check thatiat
(issued at) claim value is an integerverify_nbf=verify_signature
check thatnbf
(not before) claim value is in the past
Warning
exp
,iat
andnbf
will only be verified if present. Please pass respective value torequire
if you want to make sure that they are always present (and therefore always verified ifverify_exp
,verify_iat
, andverify_nbf
respectively is set toTrue
). - Iterable] audience (Union[str,) – optional, the value for
verify_aud
check - issuer (str) – optional, the value for
verify_iss
check - leeway (float) – a time margin in seconds for the expiration check
Return type: Returns: the JWT claims
-
jwt.api_jwt.
decode_complete
(jwt, key="", algorithms=None, options=None, audience=None, issuer=None, leeway=0)¶ Identical to
jwt.decode
except for return value which is a dictionary containing the token header (JOSE Header), the token payload (JWT Payload), and token signature (JWT Signature) on the keys “header”, “payload”, and “signature” respectively.Parameters: - jwt (str) – the token to be decoded
- key (str) – the key suitable for the allowed algorithm
- algorithms (list) –
allowed algorithms, e.g.
["ES256"]
Warning
Do not compute the
algorithms
parameter based on thealg
from the token itself, or on any other data that an attacker may be able to influence, as that might expose you to various vulnerabilities (see RFC 8725 §2.1). Instead, either hard-code a fixed value foralgorithms
, or configure it in the same place you configure thekey
. Make sure not to mix symmetric and asymmetric algorithms that interpret thekey
in different ways (e.g. HS* and RS*). - options (dict) –
extended decoding and validation options
verify_signature=True
verify the JWT cryptographic signaturerequire=[]
list of claims that must be present. Example:require=["exp", "iat", "nbf"]
. Only verifies that the claims exists. Does not verify that the claims are valid.verify_aud=verify_signature
check thataud
(audience) claim matchesaudience
verify_iss=verify_signature
check thatiss
(issuer) claim matchesissuer
verify_exp=verify_signature
check thatexp
(expiration) claim value is in the futureverify_iat=verify_signature
check thatiat
(issued at) claim value is an integerverify_nbf=verify_signature
check thatnbf
(not before) claim value is in the past
Warning
exp
,iat
andnbf
will only be verified if present. Please pass respective value torequire
if you want to make sure that they are always present (and therefore always verified ifverify_exp
,verify_iat
, andverify_nbf
respectively is set toTrue
). - audience (Iterable) – optional, the value for
verify_aud
check - issuer (str) – optional, the value for
verify_iss
check - leeway (float) – a time margin in seconds for the expiration check
Return type: Returns: Decoded JWT with the JOSE Header on the key
header
, the JWS Payload on the keypayload
, and the JWS Signature on the keysignature
.
Note
TODO: Document PyJWS class
Exceptions¶
-
class
jwt.exceptions.
InvalidTokenError
¶ Base exception when
decode()
fails on a token
-
class
jwt.exceptions.
DecodeError
¶ Raised when a token cannot be decoded because it failed validation
-
class
jwt.exceptions.
InvalidSignatureError
¶ Raised when a token’s signature doesn’t match the one provided as part of the token.
-
class
jwt.exceptions.
ExpiredSignatureError
¶ Raised when a token’s
exp
claim indicates that it has expired
-
class
jwt.exceptions.
InvalidAudienceError
¶ Raised when a token’s
aud
claim does not match one of the expected audience values
-
class
jwt.exceptions.
InvalidIssuerError
¶ Raised when a token’s
iss
claim does not match the expected issuer
-
class
jwt.exceptions.
InvalidIssuedAtError
¶ Raised when a token’s
iat
claim is in the future
-
class
jwt.exceptions.
ImmatureSignatureError
¶ Raised when a token’s
nbf
claim represents a time in the future
-
class
jwt.exceptions.
InvalidKeyError
¶ Raised when the specified key is not in the proper format
-
class
jwt.exceptions.
InvalidAlgorithmError
¶ Raised when the specified algorithm is not recognized by PyJWT
-
class
jwt.exceptions.
MissingRequiredClaimError
¶ Raised when a claim that is required to be present is not contained in the claimset
Changelog¶
All notable changes to this project will be documented in this file. This project adheres to Semantic Versioning.
v2.4.0¶
Changed¶
- Skip keys with incompatible alg when loading JWKSet by @DaGuich in #762
- Remove support for python3.6 by @sirosen in #777
- Emit a deprecation warning for unsupported kwargs by @sirosen in #776
- Remove redundant wheel dep from pyproject.toml by @mgorny in #765
- Do not fail when an unusable key occurs by @DaGuich in #762
- Update audience typing by @JulianMaurin in #782
- Improve PyJWKSet error accuracy by @JulianMaurin in #786
- Mypy as pre-commit check + api_jws typing by @JulianMaurin in #787
Fixed¶
- Adjust expected exceptions in option merging tests for PyPy3 by @mgorny in #763
- Fixes for pyright on strict mode by @brandon-leapyear in #747
- docs: fix simple typo, iinstance -> isinstance by @timgates42 in #774
- Fix typo: priot -> prior by @jdufresne in #780
- Fix for headers disorder issue by @kadabusha in #721
v2.4.0¶
Security¶
- [CVE-2022-29217] Prevent key confusion through non-blocklisted public key formats. https://github.com/jpadilla/pyjwt/security/advisories/GHSA-ffqj-6fqr-9h24
Changed¶
- Explicit check the key for ECAlgorithm by @estin in https://github.com/jpadilla/pyjwt/pull/713
- Raise DeprecationWarning for jwt.decode(verify=…) by @akx in https://github.com/jpadilla/pyjwt/pull/742
Fixed¶
- Don’t use implicit optionals by @rekyungmin in https://github.com/jpadilla/pyjwt/pull/705
- documentation fix: show correct scope for decode_complete() by @sseering in https://github.com/jpadilla/pyjwt/pull/661
- fix: Update copyright information by @kkirsche in https://github.com/jpadilla/pyjwt/pull/729
- Don’t mutate options dictionary in .decode_complete() by @akx in https://github.com/jpadilla/pyjwt/pull/743
Added¶
- Add support for Python 3.10 by @hugovk in https://github.com/jpadilla/pyjwt/pull/699
- api_jwk: Add PyJWKSet.__getitem__ by @woodruffw in https://github.com/jpadilla/pyjwt/pull/725
- Update usage.rst by @guneybilen in https://github.com/jpadilla/pyjwt/pull/727
- Docs: mention performance reasons for reusing RSAPrivateKey when encoding by @dmahr1 in https://github.com/jpadilla/pyjwt/pull/734
- Fixed typo in usage.rst by @israelabraham in https://github.com/jpadilla/pyjwt/pull/738
- Add detached payload support for JWS encoding and decoding by @fviard in https://github.com/jpadilla/pyjwt/pull/723
- Replace various string interpolations with f-strings by @akx in https://github.com/jpadilla/pyjwt/pull/744
- Update CHANGELOG.rst by @hipertracker in https://github.com/jpadilla/pyjwt/pull/751
v2.0.0¶
Changed¶
Drop support for Python 2 and Python 3.0-3.5¶
Python 3.5 is EOL so we decide to drop its support. Version 1.7.1
is
the last one supporting Python 3.0-3.5.
Require cryptography >= 3¶
Drop support for PyCrypto and ECDSA¶
We’ve kept this around for a long time, mostly for environments that didn’t allow installing cryptography.
Drop CLI¶
Dropped the included cli entry point.
Improve typings¶
We no longer need to use mypy Python 2 compatibility mode (comments)
jwt.encode(...)
return type¶
Tokens are returned as string instead of a byte string
Dropped deprecated errors¶
Removed ExpiredSignature
, InvalidAudience
, and
InvalidIssuer
. Use ExpiredSignatureError
,
InvalidAudienceError
, and InvalidIssuerError
instead.
Dropped deprecated verify_expiration
param in jwt.decode(...)
¶
Use
jwt.decode(encoded, key, algorithms=["HS256"], options={"verify_exp": False})
instead.
Dropped deprecated verify
param in jwt.decode(...)
¶
Use jwt.decode(encoded, key, options={"verify_signature": False})
instead.
Require explicit algorithms
in jwt.decode(...)
by default¶
Example: jwt.decode(encoded, key, algorithms=["HS256"])
.
Dropped deprecated require_*
options in jwt.decode(...)
¶
For example, instead of
jwt.decode(encoded, key, algorithms=["HS256"], options={"require_exp": True})
,
use
jwt.decode(encoded, key, algorithms=["HS256"], options={"require": ["exp"]})
.
And the old v1.x syntax
jwt.decode(token, verify=False)
is now:
jwt.decode(jwt=token, key='secret', algorithms=['HS256'], options={"verify_signature": False, "verify_exp": True})
Added¶
Introduce better experience for JWKs¶
Introduce PyJWK
, PyJWKSet
, and PyJWKClient
.
import jwt
from jwt import PyJWKClient
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA"
kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw"
url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
jwks_client = PyJWKClient(url)
signing_key = jwks_client.get_signing_key_from_jwt(token)
data = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience="https://expenses-api",
options={"verify_exp": False},
)
print(data)
Support for JWKs containing ECDSA keys¶
Add support for Ed25519 / EdDSA¶
Pull Requests¶
- Add PyPy3 to the test matrix (#550) by @jdufresne
- Require tweak (#280) by @psafont
- Decode return type is dict[str, Any] (#393) by @jacopofar
- Fix linter error in test_cli (#414) by @jaraco
- Run mypy with tox (#421) by @jpadilla
- Document (and prefer) pyjwt[crypto] req format (#426) by @gthb
- Correct type for json_encoder argument (#438) by @jdufresne
- Prefer https:// links where available (#439) by @jdufresne
- Pass python_requires argument to setuptools (#440) by @jdufresne
- Rename [wheel] section to [bdist_wheel] as the former is legacy (#441) by @jdufresne
- Remove setup.py test command in favor of pytest and tox (#442) by @jdufresne
- Fix mypy errors (#449) by @jpadilla
- DX Tweaks (#450) by @jpadilla
- Add support of python 3.8 (#452) by @Djailla
- Fix 406 (#454) by @justinbaur
- Add support for Ed25519 / EdDSA, with unit tests (#455) by @Someguy123
- Remove Python 2.7 compatibility (#457) by @Djailla
- Fix simple typo: encododed -> encoded (#462) by @timgates42
- Enhance tracebacks. (#477) by @JulienPalard
- Simplify
python_requires
(#478) by @michael-k - Document top-level .encode and .decode to close #459 (#482) by @dimaqq
- Improve documentation for audience usage (#484) by @CorreyL
- Correct README on how to run tests locally (#489) by @jdufresne
- Fix
tox -e lint
warnings and errors (#490) by @jdufresne - Run pyupgrade across project to use modern Python 3 conventions (#491) by @jdufresne
- Add Python-3-only trove classifier and remove “universal” from wheel (#492) by @jdufresne
- Emit warnings about user code, not pyjwt code (#494) by @mgedmin
- Move setup information to declarative setup.cfg (#495) by @jdufresne
- CLI options for verifying audience and issuer (#496) by @GeoffRichards
- Specify the target Python version for mypy (#497) by @jdufresne
- Remove unnecessary compatibility shims for Python 2 (#498) by @jdufresne
- Setup GH Actions (#499) by @jpadilla
- Implementation of ECAlgorithm.from_jwk (#500) by @jpadilla
- Remove cli entry point (#501) by @jpadilla
- Expose InvalidKeyError on jwt module (#503) by @russellcardullo
- Avoid loading token twice in pyjwt.decode (#506) by @CaselIT
- Default links to stable version of documentation (#508) by @salcedo
- Update README.md badges (#510) by @jpadilla
- Introduce better experience for JWKs (#511) by @jpadilla
- Fix tox conditional extras (#512) by @jpadilla
- Return tokens as string not bytes (#513) by @jpadilla
- Drop support for legacy contrib algorithms (#514) by @jpadilla
- Drop deprecation warnings (#515) by @jpadilla
- Update Auth0 sponsorship link (#519) by @Sambego
- Update return type for jwt.encode (#521) by @moomoolive
- Run tests against Python 3.9 and add trove classifier (#522) by @michael-k
- Removed redundant
default_backend()
(#523) by @rohitkg98 - Documents how to use private keys with passphrases (#525) by @rayluo
- Update version to 2.0.0a1 (#528) by @jpadilla
- Fix usage example (#530) by @nijel
- add EdDSA to docs (#531) by @CircleOnCircles
- Remove support for EOL Python 3.5 (#532) by @jdufresne
- Upgrade to isort 5 and adjust configurations (#533) by @jdufresne
- Remove unused argument “verify” from PyJWS.decode() (#534) by @jdufresne
- Update typing syntax and usage for Python 3.6+ (#535) by @jdufresne
- Run pyupgrade to simplify code and use Python 3.6 syntax (#536) by @jdufresne
- Drop unknown pytest config option: strict (#537) by @jdufresne
- Upgrade black version and usage (#538) by @jdufresne
- Remove “Command line” sections from docs (#539) by @jdufresne
- Use existing key_path() utility function throughout tests (#540) by @jdufresne
- Replace force_bytes()/force_unicode() in tests with literals (#541) by @jdufresne
- Remove unnecessary Unicode decoding before json.loads() (#542) by @jdufresne
- Remove unnecessary force_bytes() calls prior to base64url_decode() (#543) by @jdufresne
- Remove deprecated arguments from docs (#544) by @jdufresne
- Update code blocks in docs (#545) by @jdufresne
- Refactor jwt/jwks_client.py without requests dependency (#546) by @jdufresne
- Tighten bytes/str boundaries and remove unnecessary coercing (#547) by @jdufresne
- Replace codecs.open() with builtin open() (#548) by @jdufresne
- Replace int_from_bytes() with builtin int.from_bytes() (#549) by @jdufresne
- Enforce .encode() return type using mypy (#551) by @jdufresne
- Prefer direct indexing over options.get() (#552) by @jdufresne
- Cleanup “noqa” comments (#553) by @jdufresne
- Replace merge_dict() with builtin dict unpacking generalizations (#555) by @jdufresne
- Do not mutate the input payload in PyJWT.encode() (#557) by @jdufresne
- Use direct indexing in PyJWKClient.get_signing_key_from_jwt() (#558) by @jdufresne
- Split PyJWT/PyJWS classes to tighten type interfaces (#559) by @jdufresne
- Simplify mocked_response test utility function (#560) by @jdufresne
- Autoupdate pre-commit hooks and apply them (#561) by @jdufresne
- Remove unused argument “payload” from PyJWS.verifysignature() (#562) by @jdufresne
- Add utility functions to assist test skipping (#563) by @jdufresne
- Type hint jwt.utils module (#564) by @jdufresne
- Prefer ModuleNotFoundError over ImportError (#565) by @jdufresne
- Fix tox “manifest” environment to pass (#566) by @jdufresne
- Fix tox “docs” environment to pass (#567) by @jdufresne
- Simplify black configuration to be closer to upstream defaults (#568) by @jdufresne
- Use generator expressions (#569) by @jdufresne
- Simplify from_base64url_uint() (#570) by @jdufresne
- Drop lint environment from GitHub actions in favor of pre-commit.ci (#571) by @jdufresne
- [pre-commit.ci] pre-commit autoupdate (#572)
- Simplify tox configuration (#573) by @jdufresne
- Combine identical test functions using pytest.mark.parametrize() (#574) by @jdufresne
- Complete type hinting of jwks_client.py (#578) by @jdufresne
v1.5.0¶
Changed¶
- Add support for ECDSA public keys in RFC 4253 (OpenSSH) format #244
- Renamed commandline script
jwt
tojwt-cli
to avoid issues with the script clobbering thejwt
module in some circumstances. #187 - Better error messages when using an algorithm that requires the cryptography package, but it isn’t available #230
- Tokens with future ‘iat’ values are no longer rejected #190
- Non-numeric ‘iat’ values now raise InvalidIssuedAtError instead of DecodeError
- Remove rejection of future ‘iat’ claims #252
Fixed¶
v1.4¶
Fixed¶
- Exclude Python cache files from PyPI releases.
Added¶
- Added new options to require certain claims (require_nbf,
require_iat, require_exp) and raise
MissingRequiredClaimError
if they are not present. - If
audience=
orissuer=
is specified but the claim is not present,MissingRequiredClaimError
is now raised instead ofInvalidAudienceError
andInvalidIssuerError
v1.3¶
Fixed¶
Added¶
- Added a new
jwt.get_unverified_header()
to parse and return the header portion of a token prior to signature verification.
Removed¶
- Python 3.2 is no longer a supported platform. This version of Python is rarely used. Users affected by this should upgrade to 3.3+.
v1.2.0¶
Fixed¶
- Added back
verify_expiration=
argument tojwt.decode()
that was erroneously removed in v1.1.0.
Deprecated¶
verify_expiration=
argument tojwt.decode()
is now deprecated and will be removed in a future version. Use theoption=
argument instead.