back to OpenHands summary
OpenHands: pyjwt
Pytest Summary for test tests
status |
count |
failed |
96 |
passed |
163 |
skipped |
2 |
xfailed |
1 |
total |
262 |
collected |
262 |
Failed pytests:
test_advisory.py::TestAdvisory::test_ghsa_ffqj_6fqr_9h24
test_advisory.py::TestAdvisory::test_ghsa_ffqj_6fqr_9h24
self =
jwt = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoxMjM0fQ.6ulDpqSlbHmQ8bZXhZRLFko9SwcHrghCwh8d-exJEE4'
key = b'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPL1I9oiq+B8crkmuV4YViiUnhdLjCp3hvy1bNGuGfNL'
algorithms = ['none', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', ...]
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoxMjM0fQ'
crypto_segment = b'6ulDpqSlbHmQ8bZXhZRLFko9SwcHrghCwh8d-exJEE4'
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
raise InvalidAlgorithmError('The specified alg value is not allowed')
if alg == 'none':
if merged_options['verify_signature']:
raise DecodeError('Algorithm "none" not allowed')
if key not in [None, '', 'none']:
raise InvalidKeyError('When alg = "none", key must be empty or "none"')
if signature != b'':
raise InvalidSignatureError('Signature verification failed')
return {
'header': header,
'payload': payload,
'signature': signature
}
try:
alg_obj = self._algorithms[alg]
except KeyError:
raise InvalidAlgorithmError('Algorithm not supported')
if merged_options['verify_signature']:
try:
if key is None:
raise InvalidKeyError('Key is required when algorithm is not "none"')
> key = alg_obj.prepare_key(key)
jwt/api_jws.py:302:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
key = b'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPL1I9oiq+B8crkmuV4YViiUnhdLjCp3hvy1bNGuGfNL'
def prepare_key(self, key: Union[str, bytes]) -> bytes:
if not isinstance(key, (str, bytes)):
raise TypeError('Expected a string value')
key = force_bytes(key)
if is_pem_format(key) or is_ssh_key(key):
> raise InvalidKeyError('The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.')
E jwt.exceptions.InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.
jwt/algorithms.py:168: InvalidKeyError
During handling of the above exception, another exception occurred:
self =
@crypto_required
def test_ghsa_ffqj_6fqr_9h24(self):
# Generate ed25519 private key
# private_key = ed25519.Ed25519PrivateKey.generate()
# Get private key bytes as they would be stored in a file
# priv_key_bytes = private_key.private_bytes(
# encoding=serialization.Encoding.PEM,
# format=serialization.PrivateFormat.PKCS8,
# encryption_algorithm=serialization.NoEncryption(),
# )
# Get public key bytes as they would be stored in a file
# pub_key_bytes = private_key.public_key().public_bytes(
# encoding=serialization.Encoding.OpenSSH,
# format=serialization.PublicFormat.OpenSSH,
# )
# Making a good jwt token that should work by signing it
# with the private key
# encoded_good = jwt.encode({"test": 1234}, priv_key_bytes, algorithm="EdDSA")
encoded_good = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0IjoxMjM0fQ.M5y1EEavZkHSlj9i8yi9nXKKyPBSAUhDRTOYZi3zZY11tZItDaR3qwAye8pc74_lZY3Ogt9KPNFbVOSGnUBHDg"
# Using HMAC with the public key to trick the receiver to think that the
# public key is a HMAC secret
encoded_bad = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoxMjM0fQ.6ulDpqSlbHmQ8bZXhZRLFko9SwcHrghCwh8d-exJEE4"
algorithm_names = list(get_default_algorithms())
# Both of the jwt tokens are validated as valid
jwt.decode(
encoded_good,
pub_key_bytes,
algorithms=algorithm_names,
)
with pytest.raises(InvalidKeyError):
> jwt.decode(
encoded_bad,
pub_key_bytes,
algorithms=algorithm_names,
)
tests/test_advisory.py:64:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoxMjM0fQ.6ulDpqSlbHmQ8bZXhZRLFko9SwcHrghCwh8d-exJEE4'
key = b'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPL1I9oiq+B8crkmuV4YViiUnhdLjCp3hvy1bNGuGfNL'
algorithms = ['none', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', ...]
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoxMjM0fQ'
crypto_segment = b'6ulDpqSlbHmQ8bZXhZRLFko9SwcHrghCwh8d-exJEE4'
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
raise InvalidAlgorithmError('The specified alg value is not allowed')
if alg == 'none':
if merged_options['verify_signature']:
raise DecodeError('Algorithm "none" not allowed')
if key not in [None, '', 'none']:
raise InvalidKeyError('When alg = "none", key must be empty or "none"')
if signature != b'':
raise InvalidSignatureError('Signature verification failed')
return {
'header': header,
'payload': payload,
'signature': signature
}
try:
alg_obj = self._algorithms[alg]
except KeyError:
raise InvalidAlgorithmError('Algorithm not supported')
if merged_options['verify_signature']:
try:
if key is None:
raise InvalidKeyError('Key is required when algorithm is not "none"')
key = alg_obj.prepare_key(key)
> except InvalidKeyError:
E NameError: name 'InvalidKeyError' is not defined
jwt/api_jws.py:303: NameError
test_algorithms.py::TestAlgorithms::test_ec_jwk_public_and_private_keys_should_parse_and_verify
test_algorithms.py::TestAlgorithms::test_ec_jwk_public_and_private_keys_should_parse_and_verify
self =
@crypto_required
def test_ec_jwk_public_and_private_keys_should_parse_and_verify(self):
tests = {
"P-256": ECAlgorithm.SHA256,
"P-384": ECAlgorithm.SHA384,
"P-521": ECAlgorithm.SHA512,
"secp256k1": ECAlgorithm.SHA256,
}
for curve, hash in tests.items():
algo = ECAlgorithm(hash)
with open(key_path(f"jwk_ec_pub_{curve}.json")) as keyfile:
> pub_key = cast(EllipticCurvePublicKey, algo.from_jwk(keyfile.read()))
tests/test_algorithms.py:174:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwk = {'crv': 'secp256k1', 'kid': 'bilbo.baggins.256k@hobbiton.example', 'kty': 'EC', 'x': 'MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs', ...}
@staticmethod
def from_jwk(jwk: Union[str, JWKDict]) -> Union[EllipticCurvePrivateKey, EllipticCurvePublicKey]:
if isinstance(jwk, str):
jwk = json.loads(jwk)
if not isinstance(jwk, dict):
raise InvalidKeyError('Key must be a dict or a string')
if jwk.get('kty') != 'EC':
raise InvalidKeyError('Not an EC key')
> curve = {
'P-256K': SECP256K1,
'P-256': SECP256R1,
'P-384': SECP384R1,
'P-521': SECP521R1
}[jwk['crv']]()
E KeyError: 'secp256k1'
jwt/algorithms.py:421: KeyError
test_algorithms.py::TestAlgorithms::test_ec_jwk_fails_on_invalid_json
test_algorithms.py::TestAlgorithms::test_ec_jwk_fails_on_invalid_json
self =
@crypto_required
def test_ec_jwk_fails_on_invalid_json(self):
algo = ECAlgorithm(ECAlgorithm.SHA512)
valid_points = {
"P-256": {
"x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4",
"y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU",
},
"P-384": {
"x": "IDC-5s6FERlbC4Nc_4JhKW8sd51AhixtMdNUtPxhRFP323QY6cwWeIA3leyZhz-J",
"y": "eovmN9ocANS8IJxDAGSuC1FehTq5ZFLJU7XSPg36zHpv4H2byKGEcCBiwT4sFJsy",
},
"P-521": {
"x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
"y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1",
},
"secp256k1": {
"x": "MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs",
"y": "7r8d-xF7QAgT5kSRdly6M8xeg4Jz83Gs_CQPQRH65QI",
},
}
# Invalid JSON
with pytest.raises(InvalidKeyError):
> algo.from_jwk("")
tests/test_algorithms.py:207:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/algorithms.py:415: in from_jwk
jwk = json.loads(jwk)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/__init__.py:346: in loads
return _default_decoder.decode(s)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/decoder.py:337: in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
s = "", idx = 0
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` beginning with
a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
> raise JSONDecodeError("Expecting value", s, err.value) from None
E json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/decoder.py:355: JSONDecodeError
test_algorithms.py::TestAlgorithms::test_ec_to_jwk_raises_exception_on_invalid_key
test_algorithms.py::TestAlgorithms::test_ec_to_jwk_raises_exception_on_invalid_key
self =
@crypto_required
def test_ec_to_jwk_raises_exception_on_invalid_key(self):
algo = ECAlgorithm(ECAlgorithm.SHA256)
with pytest.raises(InvalidKeyError):
> algo.to_jwk({"not": "a valid key"}) # type: ignore[call-overload]
tests/test_algorithms.py:317:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
key_obj = {'not': 'a valid key'}, as_dict = False
@staticmethod
def to_jwk(key_obj: Union[EllipticCurvePrivateKey, EllipticCurvePublicKey], as_dict: bool = False) -> Union[str, JWKDict]:
if isinstance(key_obj, EllipticCurvePrivateKey):
numbers = key_obj.private_numbers()
jwk = {
'kty': 'EC',
'crv': {
SECP256K1: 'P-256K',
SECP256R1: 'P-256',
SECP384R1: 'P-384',
SECP521R1: 'P-521'
}[type(numbers.public_numbers.curve)],
'x': to_base64url_uint(numbers.public_numbers.x).decode('ascii'),
'y': to_base64url_uint(numbers.public_numbers.y).decode('ascii'),
'd': to_base64url_uint(numbers.private_value).decode('ascii')
}
else:
> numbers = key_obj.public_numbers()
E AttributeError: 'dict' object has no attribute 'public_numbers'
jwt/algorithms.py:395: AttributeError
test_algorithms.py::TestAlgorithms::test_ec_to_jwk_with_valid_curves[False]
test_algorithms.py::TestAlgorithms::test_ec_to_jwk_with_valid_curves[False]
self =
as_dict = False
@crypto_required
@pytest.mark.parametrize("as_dict", (False, True))
def test_ec_to_jwk_with_valid_curves(self, as_dict):
tests = {
"P-256": ECAlgorithm.SHA256,
"P-384": ECAlgorithm.SHA384,
"P-521": ECAlgorithm.SHA512,
"secp256k1": ECAlgorithm.SHA256,
}
for curve, hash in tests.items():
algo = ECAlgorithm(hash)
with open(key_path(f"jwk_ec_pub_{curve}.json")) as keyfile:
> pub_key = algo.from_jwk(keyfile.read())
tests/test_algorithms.py:332:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwk = {'crv': 'secp256k1', 'kid': 'bilbo.baggins.256k@hobbiton.example', 'kty': 'EC', 'x': 'MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs', ...}
@staticmethod
def from_jwk(jwk: Union[str, JWKDict]) -> Union[EllipticCurvePrivateKey, EllipticCurvePublicKey]:
if isinstance(jwk, str):
jwk = json.loads(jwk)
if not isinstance(jwk, dict):
raise InvalidKeyError('Key must be a dict or a string')
if jwk.get('kty') != 'EC':
raise InvalidKeyError('Not an EC key')
> curve = {
'P-256K': SECP256K1,
'P-256': SECP256R1,
'P-384': SECP384R1,
'P-521': SECP521R1
}[jwk['crv']]()
E KeyError: 'secp256k1'
jwt/algorithms.py:421: KeyError
test_algorithms.py::TestAlgorithms::test_ec_to_jwk_with_valid_curves[True]
test_algorithms.py::TestAlgorithms::test_ec_to_jwk_with_valid_curves[True]
self =
as_dict = True
@crypto_required
@pytest.mark.parametrize("as_dict", (False, True))
def test_ec_to_jwk_with_valid_curves(self, as_dict):
tests = {
"P-256": ECAlgorithm.SHA256,
"P-384": ECAlgorithm.SHA384,
"P-521": ECAlgorithm.SHA512,
"secp256k1": ECAlgorithm.SHA256,
}
for curve, hash in tests.items():
algo = ECAlgorithm(hash)
with open(key_path(f"jwk_ec_pub_{curve}.json")) as keyfile:
> pub_key = algo.from_jwk(keyfile.read())
tests/test_algorithms.py:332:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwk = {'crv': 'secp256k1', 'kid': 'bilbo.baggins.256k@hobbiton.example', 'kty': 'EC', 'x': 'MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs', ...}
@staticmethod
def from_jwk(jwk: Union[str, JWKDict]) -> Union[EllipticCurvePrivateKey, EllipticCurvePublicKey]:
if isinstance(jwk, str):
jwk = json.loads(jwk)
if not isinstance(jwk, dict):
raise InvalidKeyError('Key must be a dict or a string')
if jwk.get('kty') != 'EC':
raise InvalidKeyError('Not an EC key')
> curve = {
'P-256K': SECP256K1,
'P-256': SECP256R1,
'P-384': SECP384R1,
'P-521': SECP521R1
}[jwk['crv']]()
E KeyError: 'secp256k1'
jwt/algorithms.py:421: KeyError
test_algorithms.py::TestAlgorithms::test_ec_to_jwk_with_invalid_curve
test_algorithms.py::TestAlgorithms::test_ec_to_jwk_with_invalid_curve
self =
@crypto_required
def test_ec_to_jwk_with_invalid_curve(self):
algo = ECAlgorithm(ECAlgorithm.SHA256)
with open(key_path("testkey_ec_secp192r1.priv")) as keyfile:
priv_key = algo.prepare_key(keyfile.read())
with pytest.raises(InvalidKeyError):
> algo.to_jwk(priv_key)
tests/test_algorithms.py:357:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
key_obj =
as_dict = False
@staticmethod
def to_jwk(key_obj: Union[EllipticCurvePrivateKey, EllipticCurvePublicKey], as_dict: bool = False) -> Union[str, JWKDict]:
if isinstance(key_obj, EllipticCurvePrivateKey):
numbers = key_obj.private_numbers()
jwk = {
'kty': 'EC',
> 'crv': {
SECP256K1: 'P-256K',
SECP256R1: 'P-256',
SECP384R1: 'P-384',
SECP521R1: 'P-521'
}[type(numbers.public_numbers.curve)],
'x': to_base64url_uint(numbers.public_numbers.x).decode('ascii'),
'y': to_base64url_uint(numbers.public_numbers.y).decode('ascii'),
'd': to_base64url_uint(numbers.private_value).decode('ascii')
}
E KeyError:
jwt/algorithms.py:384: KeyError
test_algorithms.py::TestAlgorithms::test_rsa_jwk_private_key_with_other_primes_is_invalid
test_algorithms.py::TestAlgorithms::test_rsa_jwk_private_key_with_other_primes_is_invalid
self =
@crypto_required
def test_rsa_jwk_private_key_with_other_primes_is_invalid(self):
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
with open(key_path("jwk_rsa_key.json")) as keyfile:
> with pytest.raises(InvalidKeyError):
E Failed: DID NOT RAISE
tests/test_algorithms.py:401: Failed
test_algorithms.py::TestAlgorithms::test_rsa_jwk_private_key_with_missing_values_is_invalid
test_algorithms.py::TestAlgorithms::test_rsa_jwk_private_key_with_missing_values_is_invalid
self =
@crypto_required
def test_rsa_jwk_private_key_with_missing_values_is_invalid(self):
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
with open(key_path("jwk_rsa_key.json")) as keyfile:
> with pytest.raises(InvalidKeyError):
E Failed: DID NOT RAISE
tests/test_algorithms.py:412: Failed
test_algorithms.py::TestAlgorithms::test_rsa_jwk_private_key_can_recover_prime_factors
test_algorithms.py::TestAlgorithms::test_rsa_jwk_private_key_can_recover_prime_factors
self =
@crypto_required
def test_rsa_jwk_private_key_can_recover_prime_factors(self):
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
with open(key_path("jwk_rsa_key.json")) as keyfile:
keybytes = keyfile.read()
control_key = cast(RSAPrivateKey, algo.from_jwk(keybytes)).private_numbers()
keydata = json.loads(keybytes)
delete_these = ["p", "q", "dp", "dq", "qi"]
for field in delete_these:
del keydata[field]
parsed_key = cast(
RSAPrivateKey, algo.from_jwk(json.dumps(keydata))
> ).private_numbers()
E AttributeError: 'cryptography.hazmat.bindings._rust.openssl.rsa.RSAPublicKey' object has no attribute 'private_numbers'
tests/test_algorithms.py:433: AttributeError
test_algorithms.py::TestAlgorithms::test_rsa_jwk_private_key_with_missing_required_values_is_invalid
test_algorithms.py::TestAlgorithms::test_rsa_jwk_private_key_with_missing_required_values_is_invalid
self =
@crypto_required
def test_rsa_jwk_private_key_with_missing_required_values_is_invalid(self):
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
with open(key_path("jwk_rsa_key.json")) as keyfile:
> with pytest.raises(InvalidKeyError):
E Failed: DID NOT RAISE
tests/test_algorithms.py:447: Failed
test_algorithms.py::TestAlgorithms::test_rsa_jwk_raises_exception_if_not_a_valid_key
test_algorithms.py::TestAlgorithms::test_rsa_jwk_raises_exception_if_not_a_valid_key
self =
@crypto_required
def test_rsa_jwk_raises_exception_if_not_a_valid_key(self):
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
# Invalid JSON
with pytest.raises(InvalidKeyError):
> algo.from_jwk("{not-a-real-key")
tests/test_algorithms.py:459:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/algorithms.py:293: in from_jwk
jwk = json.loads(jwk)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/__init__.py:346: in loads
return _default_decoder.decode(s)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/decoder.py:337: in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
s = '{not-a-real-key', idx = 0
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` beginning with
a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
> obj, end = self.scan_once(s, idx)
E json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/decoder.py:353: JSONDecodeError
test_algorithms.py::TestAlgorithms::test_rsa_to_jwk_returns_correct_values_for_public_key[False]
test_algorithms.py::TestAlgorithms::test_rsa_to_jwk_returns_correct_values_for_public_key[False]
self =
as_dict = False
@crypto_required
@pytest.mark.parametrize("as_dict", (False, True))
def test_rsa_to_jwk_returns_correct_values_for_public_key(self, as_dict):
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
with open(key_path("testkey_rsa.pub")) as keyfile:
pub_key = algo.prepare_key(keyfile.read())
key: Any = algo.to_jwk(pub_key, as_dict=as_dict)
if not as_dict:
key = json.loads(key)
expected = {
"e": "AQAB",
"key_ops": ["verify"],
"kty": "RSA",
"n": (
"1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJdCvuCJRr-xCN-"
"OmO8qwgJJR98feNujxVg-J9Ls3_UOA4HcF9nYH6aqVXELAE8Hk_ALvxi96ms"
"1DDuAvQGaYZ-lANxlvxeQFOZSbjkz_9mh8aLeGKwqJLp3p-OhUBQpwvAUAPg"
"82-OUtgTW3nSljjeFr14B8qAneGSc_wl0ni--1SRZUXFSovzcqQOkla3W27r"
"rLfrD6LXgj_TsDs4vD1PnIm1zcVenKT7TfYI17bsG_O_Wecwz2Nl19pL7gDo"
"sNruF3ogJWNq1Lyn_ijPQnkPLpZHyhvuiycYcI3DiQ"
),
}
> assert key == expected
E AssertionError: assert {'e': 'AQAB',...vuiycYcI3DiQ'} == {'e': 'AQAB',...vuiycYcI3DiQ'}
E
E Omitting 3 identical items, use -vv to show
E Right contains 1 more item:
E {'key_ops': ['verify']}
E Use -v to get more diff
tests/test_algorithms.py:491: AssertionError
test_algorithms.py::TestAlgorithms::test_rsa_to_jwk_returns_correct_values_for_public_key[True]
test_algorithms.py::TestAlgorithms::test_rsa_to_jwk_returns_correct_values_for_public_key[True]
self =
as_dict = True
@crypto_required
@pytest.mark.parametrize("as_dict", (False, True))
def test_rsa_to_jwk_returns_correct_values_for_public_key(self, as_dict):
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
with open(key_path("testkey_rsa.pub")) as keyfile:
pub_key = algo.prepare_key(keyfile.read())
key: Any = algo.to_jwk(pub_key, as_dict=as_dict)
if not as_dict:
key = json.loads(key)
expected = {
"e": "AQAB",
"key_ops": ["verify"],
"kty": "RSA",
"n": (
"1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJdCvuCJRr-xCN-"
"OmO8qwgJJR98feNujxVg-J9Ls3_UOA4HcF9nYH6aqVXELAE8Hk_ALvxi96ms"
"1DDuAvQGaYZ-lANxlvxeQFOZSbjkz_9mh8aLeGKwqJLp3p-OhUBQpwvAUAPg"
"82-OUtgTW3nSljjeFr14B8qAneGSc_wl0ni--1SRZUXFSovzcqQOkla3W27r"
"rLfrD6LXgj_TsDs4vD1PnIm1zcVenKT7TfYI17bsG_O_Wecwz2Nl19pL7gDo"
"sNruF3ogJWNq1Lyn_ijPQnkPLpZHyhvuiycYcI3DiQ"
),
}
> assert key == expected
E AssertionError: assert {'e': 'AQAB',...vuiycYcI3DiQ'} == {'e': 'AQAB',...vuiycYcI3DiQ'}
E
E Omitting 3 identical items, use -vv to show
E Right contains 1 more item:
E {'key_ops': ['verify']}
E Use -v to get more diff
tests/test_algorithms.py:491: AssertionError
test_algorithms.py::TestAlgorithms::test_rsa_to_jwk_returns_correct_values_for_private_key[False]
test_algorithms.py::TestAlgorithms::test_rsa_to_jwk_returns_correct_values_for_private_key[False]
self =
as_dict = False
@crypto_required
@pytest.mark.parametrize("as_dict", (False, True))
def test_rsa_to_jwk_returns_correct_values_for_private_key(self, as_dict):
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
with open(key_path("testkey_rsa.priv")) as keyfile:
priv_key = algo.prepare_key(keyfile.read())
key: Any = algo.to_jwk(priv_key, as_dict=as_dict)
if not as_dict:
key = json.loads(key)
expected = {
"key_ops": ["sign"],
"kty": "RSA",
"e": "AQAB",
"n": (
"1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJdCvuCJRr-xCN-"
"OmO8qwgJJR98feNujxVg-J9Ls3_UOA4HcF9nYH6aqVXELAE8Hk_ALvxi96ms"
"1DDuAvQGaYZ-lANxlvxeQFOZSbjkz_9mh8aLeGKwqJLp3p-OhUBQpwvAUAPg"
"82-OUtgTW3nSljjeFr14B8qAneGSc_wl0ni--1SRZUXFSovzcqQOkla3W27r"
"rLfrD6LXgj_TsDs4vD1PnIm1zcVenKT7TfYI17bsG_O_Wecwz2Nl19pL7gDo"
"sNruF3ogJWNq1Lyn_ijPQnkPLpZHyhvuiycYcI3DiQ"
),
"d": (
"rfbs8AWdB1RkLJRlC51LukrAvYl5UfU1TE6XRa4o-DTg2-03OXLNEMyVpMr"
"a47weEnu14StypzC8qXL7vxXOyd30SSFTffLfleaTg-qxgMZSDw-Fb_M-pU"
"HMPMEDYG-lgGma4l4fd1yTX2ATtoUo9BVOQgWS1LMZqi0ASEOkUfzlBgL04"
"UoaLhPSuDdLygdlDzgruVPnec0t1uOEObmrcWIkhwU2CGQzeLtuzX6OVgPh"
"k7xcnjbDurTTVpWH0R0gbZ5ukmQ2P-YuCX8T9iWNMGjPNSkb7h02s2Oe9ZR"
"zP007xQ0VF-Z7xyLuxk6ASmoX1S39ujSbk2WF0eXNPRgFwQ"
),
"q": (
"47hlW2f1ARuWYJf9Dl6MieXjdj2dGx9PL2UH0unVzJYInd56nqXNPrQrc5k"
"ZU65KApC9n9oKUwIxuqwAAbh8oGNEQDqnuTj-powCkdC6bwA8KH1Y-wotpq"
"_GSjxkNzjWRm2GArJSzZc6Fb8EuObOrAavKJ285-zMPCEfus1WZG0"
),
"p": (
"7tr0z929Lp4OHIRJjIKM_rDrWMPtRgnV-51pgWsN6qdpDzns_PgFwrHcoyY"
"sWIO-4yCdVWPxFOgEZ8xXTM_uwOe4VEmdZhw55Tx7axYZtmZYZbO_RIP4CG"
"mlJlOFTiYnxpr-2Cx6kIeQmd-hf7fA3tL018aEzwYMbFMcnAGnEg0"
),
"qi": (
"djo95mB0LVYikNPa-NgyDwLotLqrueb9IviMmn6zKHCwiOXReqXDX9slB8"
"RA15uv56bmN04O__NyVFcgJ2ef169GZHiRFIgIy0Pl8LYkMhCYKKhyqM7g"
"xN-SqGqDTKDC22j00S7jcvCaa1qadn1qbdfukZ4NXv7E2d_LO0Y2Kkc"
),
"dp": (
"tgZ2-tJpEdWxu1m1EzeKa644LHVjpTRptk7H0LDc8i6SieADEuWQvkb9df"
"fpY6tDFaQNQr3fQ6dtdAztmsP7l1b_ynwvT1nDZUcqZvl4ruBgDWFmKbjI"
"lOCt0v9jX6MEPP5xqBx9axdkw18BnGtUuHrbzHSlUX-yh_rumpVH1SE"
),
"dq": (
"xxCIuhD0YlWFbUcwFgGdBWcLIm_WCMGj7SB6aGu1VDTLr4Wu10TFWM0TNu"
"hc9YPker2gpj5qzAmdAzwcfWSSvXpJTYR43jfulBTMoj8-2o3wCM0anclW"
"AuKhin-kc4mh9ssDXRQZwlMymZP0QtaxUDw_nlfVrUCZgO7L1_ZsUTk"
),
}
> assert key == expected
E AssertionError: assert {'d': 'rfbs8A...: 'AQAB', ...} == {'d': 'rfbs8A...: 'AQAB', ...}
E
E Omitting 9 identical items, use -vv to show
E Right contains 1 more item:
E {'key_ops': ['sign']}
E Use -v to get more diff
tests/test_algorithms.py:552: AssertionError
test_algorithms.py::TestAlgorithms::test_rsa_to_jwk_returns_correct_values_for_private_key[True]
test_algorithms.py::TestAlgorithms::test_rsa_to_jwk_returns_correct_values_for_private_key[True]
self =
as_dict = True
@crypto_required
@pytest.mark.parametrize("as_dict", (False, True))
def test_rsa_to_jwk_returns_correct_values_for_private_key(self, as_dict):
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
with open(key_path("testkey_rsa.priv")) as keyfile:
priv_key = algo.prepare_key(keyfile.read())
key: Any = algo.to_jwk(priv_key, as_dict=as_dict)
if not as_dict:
key = json.loads(key)
expected = {
"key_ops": ["sign"],
"kty": "RSA",
"e": "AQAB",
"n": (
"1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJdCvuCJRr-xCN-"
"OmO8qwgJJR98feNujxVg-J9Ls3_UOA4HcF9nYH6aqVXELAE8Hk_ALvxi96ms"
"1DDuAvQGaYZ-lANxlvxeQFOZSbjkz_9mh8aLeGKwqJLp3p-OhUBQpwvAUAPg"
"82-OUtgTW3nSljjeFr14B8qAneGSc_wl0ni--1SRZUXFSovzcqQOkla3W27r"
"rLfrD6LXgj_TsDs4vD1PnIm1zcVenKT7TfYI17bsG_O_Wecwz2Nl19pL7gDo"
"sNruF3ogJWNq1Lyn_ijPQnkPLpZHyhvuiycYcI3DiQ"
),
"d": (
"rfbs8AWdB1RkLJRlC51LukrAvYl5UfU1TE6XRa4o-DTg2-03OXLNEMyVpMr"
"a47weEnu14StypzC8qXL7vxXOyd30SSFTffLfleaTg-qxgMZSDw-Fb_M-pU"
"HMPMEDYG-lgGma4l4fd1yTX2ATtoUo9BVOQgWS1LMZqi0ASEOkUfzlBgL04"
"UoaLhPSuDdLygdlDzgruVPnec0t1uOEObmrcWIkhwU2CGQzeLtuzX6OVgPh"
"k7xcnjbDurTTVpWH0R0gbZ5ukmQ2P-YuCX8T9iWNMGjPNSkb7h02s2Oe9ZR"
"zP007xQ0VF-Z7xyLuxk6ASmoX1S39ujSbk2WF0eXNPRgFwQ"
),
"q": (
"47hlW2f1ARuWYJf9Dl6MieXjdj2dGx9PL2UH0unVzJYInd56nqXNPrQrc5k"
"ZU65KApC9n9oKUwIxuqwAAbh8oGNEQDqnuTj-powCkdC6bwA8KH1Y-wotpq"
"_GSjxkNzjWRm2GArJSzZc6Fb8EuObOrAavKJ285-zMPCEfus1WZG0"
),
"p": (
"7tr0z929Lp4OHIRJjIKM_rDrWMPtRgnV-51pgWsN6qdpDzns_PgFwrHcoyY"
"sWIO-4yCdVWPxFOgEZ8xXTM_uwOe4VEmdZhw55Tx7axYZtmZYZbO_RIP4CG"
"mlJlOFTiYnxpr-2Cx6kIeQmd-hf7fA3tL018aEzwYMbFMcnAGnEg0"
),
"qi": (
"djo95mB0LVYikNPa-NgyDwLotLqrueb9IviMmn6zKHCwiOXReqXDX9slB8"
"RA15uv56bmN04O__NyVFcgJ2ef169GZHiRFIgIy0Pl8LYkMhCYKKhyqM7g"
"xN-SqGqDTKDC22j00S7jcvCaa1qadn1qbdfukZ4NXv7E2d_LO0Y2Kkc"
),
"dp": (
"tgZ2-tJpEdWxu1m1EzeKa644LHVjpTRptk7H0LDc8i6SieADEuWQvkb9df"
"fpY6tDFaQNQr3fQ6dtdAztmsP7l1b_ynwvT1nDZUcqZvl4ruBgDWFmKbjI"
"lOCt0v9jX6MEPP5xqBx9axdkw18BnGtUuHrbzHSlUX-yh_rumpVH1SE"
),
"dq": (
"xxCIuhD0YlWFbUcwFgGdBWcLIm_WCMGj7SB6aGu1VDTLr4Wu10TFWM0TNu"
"hc9YPker2gpj5qzAmdAzwcfWSSvXpJTYR43jfulBTMoj8-2o3wCM0anclW"
"AuKhin-kc4mh9ssDXRQZwlMymZP0QtaxUDw_nlfVrUCZgO7L1_ZsUTk"
),
}
> assert key == expected
E AssertionError: assert {'d': 'rfbs8A...: 'AQAB', ...} == {'d': 'rfbs8A...: 'AQAB', ...}
E
E Omitting 9 identical items, use -vv to show
E Right contains 1 more item:
E {'key_ops': ['sign']}
E Use -v to get more diff
tests/test_algorithms.py:552: AssertionError
test_algorithms.py::TestAlgorithms::test_rsa_to_jwk_raises_exception_on_invalid_key
test_algorithms.py::TestAlgorithms::test_rsa_to_jwk_raises_exception_on_invalid_key
self =
@crypto_required
def test_rsa_to_jwk_raises_exception_on_invalid_key(self):
algo = RSAAlgorithm(RSAAlgorithm.SHA256)
with pytest.raises(InvalidKeyError):
> algo.to_jwk({"not": "a valid key"}) # type: ignore[call-overload]
tests/test_algorithms.py:559:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
key_obj = {'not': 'a valid key'}, as_dict = False
@staticmethod
def to_jwk(key_obj: Union[RSAPrivateKey, RSAPublicKey], as_dict: bool = False) -> Union[str, JWKDict]:
if isinstance(key_obj, RSAPrivateKey):
numbers = key_obj.private_numbers()
jwk = {
'kty': 'RSA',
'n': to_base64url_uint(numbers.public_numbers.n).decode('ascii'),
'e': to_base64url_uint(numbers.public_numbers.e).decode('ascii'),
'd': to_base64url_uint(numbers.d).decode('ascii'),
'p': to_base64url_uint(numbers.p).decode('ascii'),
'q': to_base64url_uint(numbers.q).decode('ascii'),
'dp': to_base64url_uint(numbers.dmp1).decode('ascii'),
'dq': to_base64url_uint(numbers.dmq1).decode('ascii'),
'qi': to_base64url_uint(numbers.iqmp).decode('ascii')
}
else:
> numbers = key_obj.public_numbers()
E AttributeError: 'dict' object has no attribute 'public_numbers'
jwt/algorithms.py:279: AttributeError
test_algorithms.py::TestAlgorithms::test_ec_should_throw_exception_on_wrong_key
test_algorithms.py::TestAlgorithms::test_ec_should_throw_exception_on_wrong_key
self =
@crypto_required
def test_ec_should_throw_exception_on_wrong_key(self):
algo = ECAlgorithm(ECAlgorithm.SHA256)
> with pytest.raises(InvalidKeyError):
E Failed: DID NOT RAISE
tests/test_algorithms.py:629: Failed
test_algorithms.py::TestOKPAlgorithms::test_okp_ed25519_should_reject_non_string_key
test_algorithms.py::TestOKPAlgorithms::test_okp_ed25519_should_reject_non_string_key
self =
def test_okp_ed25519_should_reject_non_string_key(self):
algo = OKPAlgorithm()
with pytest.raises(InvalidKeyError):
> algo.prepare_key(None) # type: ignore[arg-type]
tests/test_algorithms.py:813:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/algorithms.py:493: in prepare_key
key = force_bytes(key)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
value = None
def force_bytes(value: Union[str, bytes]) -> bytes:
if isinstance(value, str):
return value.encode('utf-8')
elif isinstance(value, bytes):
return value
else:
> raise TypeError('Expected string or bytes type')
E TypeError: Expected string or bytes type
jwt/utils.py:17: TypeError
test_algorithms.py::TestOKPAlgorithms::test_okp_ed25519_jwk_fails_on_invalid_json
test_algorithms.py::TestOKPAlgorithms::test_okp_ed25519_jwk_fails_on_invalid_json
self =
def test_okp_ed25519_jwk_fails_on_invalid_json(self):
algo = OKPAlgorithm()
with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile:
valid_pub = json.loads(keyfile.read())
with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile:
valid_key = json.loads(keyfile.read())
# Invalid instance type
with pytest.raises(InvalidKeyError):
algo.from_jwk(123) # type: ignore[arg-type]
# Invalid JSON
with pytest.raises(InvalidKeyError):
> algo.from_jwk("")
tests/test_algorithms.py:919:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/algorithms.py:592: in from_jwk
jwk = json.loads(jwk)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/__init__.py:346: in loads
return _default_decoder.decode(s)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/decoder.py:337: in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
s = "", idx = 0
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` beginning with
a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
> raise JSONDecodeError("Expecting value", s, err.value) from None
E json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/decoder.py:355: JSONDecodeError
test_algorithms.py::TestOKPAlgorithms::test_okp_to_jwk_raises_exception_on_invalid_key
test_algorithms.py::TestOKPAlgorithms::test_okp_to_jwk_raises_exception_on_invalid_key
self =
def test_okp_to_jwk_raises_exception_on_invalid_key(self):
algo = OKPAlgorithm()
with pytest.raises(InvalidKeyError):
> algo.to_jwk({"not": "a valid key"}) # type: ignore[call-overload]
tests/test_algorithms.py:981:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
key_obj = {'not': 'a valid key'}, as_dict = False
@staticmethod
def to_jwk(key_obj: AllowedOKPKeys, as_dict: bool = False) -> Union[str, JWKDict]:
if isinstance(key_obj, (Ed25519PrivateKey, Ed25519PublicKey)):
crv = 'Ed25519'
elif isinstance(key_obj, (Ed448PrivateKey, Ed448PublicKey)):
crv = 'Ed448'
else:
> raise TypeError('Key must be an EdDSA key instance')
E TypeError: Key must be an EdDSA key instance
jwt/algorithms.py:556: TypeError
test_algorithms.py::TestOKPAlgorithms::test_okp_ed448_jwk_fails_on_invalid_json
test_algorithms.py::TestOKPAlgorithms::test_okp_ed448_jwk_fails_on_invalid_json
self =
def test_okp_ed448_jwk_fails_on_invalid_json(self):
algo = OKPAlgorithm()
with open(key_path("jwk_okp_pub_Ed448.json")) as keyfile:
valid_pub = json.loads(keyfile.read())
with open(key_path("jwk_okp_key_Ed448.json")) as keyfile:
valid_key = json.loads(keyfile.read())
# Invalid instance type
with pytest.raises(InvalidKeyError):
algo.from_jwk(123) # type: ignore[arg-type]
# Invalid JSON
with pytest.raises(InvalidKeyError):
> algo.from_jwk("")
tests/test_algorithms.py:1029:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/algorithms.py:592: in from_jwk
jwk = json.loads(jwk)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/__init__.py:346: in loads
return _default_decoder.decode(s)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/decoder.py:337: in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
s = "", idx = 0
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` beginning with
a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
> raise JSONDecodeError("Expecting value", s, err.value) from None
E json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/json/decoder.py:355: JSONDecodeError
test_api_jwk.py::TestPyJWK::test_should_load_key_ec_secp256k1_from_dict
test_api_jwk.py::TestPyJWK::test_should_load_key_ec_secp256k1_from_dict
self =
@crypto_required
def test_should_load_key_ec_secp256k1_from_dict(self):
with open(key_path("jwk_ec_pub_secp256k1.json")) as keyfile:
key_data = json.loads(keyfile.read())
> jwk = PyJWK.from_dict(key_data)
tests/test_api_jwk.py:118:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwk.py:69: in from_dict
return cls(obj, algorithm)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwk_data = {'crv': 'secp256k1', 'kid': 'bilbo.baggins.256k@hobbiton.example', 'kty': 'EC', 'x': 'MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs', ...}
algorithm = 'ES256K'
def __init__(self, jwk_data: JWKDict, algorithm: str | None=None) -> None:
self._algorithms = get_default_algorithms()
self._jwk_data = jwk_data
kty = self._jwk_data.get('kty', None)
if not kty:
raise InvalidKeyError(f'kty is not found: {self._jwk_data}')
if not algorithm and isinstance(self._jwk_data, dict):
algorithm = self._jwk_data.get('alg', None)
if not algorithm:
crv = self._jwk_data.get('crv', None)
if kty == 'EC':
if crv == 'P-256' or not crv:
algorithm = 'ES256'
elif crv == 'P-384':
algorithm = 'ES384'
elif crv == 'P-521':
algorithm = 'ES512'
elif crv == 'secp256k1':
algorithm = 'ES256K'
else:
raise InvalidKeyError(f'Unsupported crv: {crv}')
elif kty == 'RSA':
algorithm = 'RS256'
elif kty == 'oct':
algorithm = 'HS256'
elif kty == 'OKP':
if not crv:
raise InvalidKeyError(f'crv is not found: {self._jwk_data}')
if crv == 'Ed25519':
algorithm = 'EdDSA'
else:
raise InvalidKeyError(f'Unsupported crv: {crv}')
else:
raise InvalidKeyError(f'Unsupported kty: {kty}')
if not has_crypto and algorithm in requires_cryptography:
raise PyJWKError(f"{algorithm} requires 'cryptography' to be installed.")
self.Algorithm = self._algorithms.get(algorithm)
if not self.Algorithm:
> raise PyJWKError(f'Unable to find an algorithm for key: {self._jwk_data}')
E jwt.exceptions.PyJWKError: Unable to find an algorithm for key: {'kty': 'EC', 'kid': 'bilbo.baggins.256k@hobbiton.example', 'crv': 'secp256k1', 'x': 'MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs', 'y': '7r8d-xF7QAgT5kSRdly6M8xeg4Jz83Gs_CQPQRH65QI'}
jwt/api_jwk.py:49: PyJWKError
test_api_jws.py::TestJWS::test_non_object_options_dont_persist
test_api_jws.py::TestJWS::test_non_object_options_dont_persist
self =
jws = , payload = b'hello world'
def test_non_object_options_dont_persist(self, jws, payload):
token = jws.encode(payload, "secret")
> jws.decode(token, "secret", options={"verify_signature": False})
tests/test_api_jws.py:81:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ.', key = 'secret'
algorithms = ['ES384', 'RS384', 'EdDSA', 'HS256', 'RS256', 'RS512', ...]
options = {'verify_signature': False}, detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': False}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
raise InvalidAlgorithmError('The specified alg value is not allowed')
if alg == 'none':
if merged_options['verify_signature']:
raise DecodeError('Algorithm "none" not allowed')
if key not in [None, '', 'none']:
> raise InvalidKeyError('When alg = "none", key must be empty or "none"')
E NameError: name 'InvalidKeyError' is not defined
jwt/api_jws.py:284: NameError
test_api_jws.py::TestJWS::test_decode_missing_segments_throws_exception
test_api_jws.py::TestJWS::test_decode_missing_segments_throws_exception
self =
jwt = b'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9'
key = 'secret', algorithms = ['HS256'], options = {}, detached_payload = None
kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}
signing_input = b'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
crypto_segment = b'eyJoZWxsbyI6ICJ3b3JsZCJ9'
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
> header_segment, payload_segment = signing_input.split(b'.', 1)
E ValueError: not enough values to unpack (expected 2, got 1)
jwt/api_jws.py:234: ValueError
During handling of the above exception, another exception occurred:
self =
jws =
def test_decode_missing_segments_throws_exception(self, jws):
secret = "secret"
example_jws = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9" # Missing segment
with pytest.raises(DecodeError) as context:
> jws.decode(example_jws, secret, algorithms=["HS256"])
tests/test_api_jws.py:121:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9'
key = 'secret', algorithms = ['HS256'], options = {}, detached_payload = None
kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}
signing_input = b'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
crypto_segment = b'eyJoZWxsbyI6ICJ3b3JsZCJ9'
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
> raise InvalidTokenError('Not enough segments')
E jwt.exceptions.InvalidTokenError: Not enough segments
jwt/api_jws.py:236: InvalidTokenError
test_api_jws.py::TestJWS::test_encode_algorithm_param_should_be_case_sensitive
test_api_jws.py::TestJWS::test_encode_algorithm_param_should_be_case_sensitive
self =
jws = , payload = b'hello world'
def test_encode_algorithm_param_should_be_case_sensitive(self, jws, payload):
jws.encode(payload, "secret", algorithm="HS256")
with pytest.raises(NotImplementedError) as context:
> jws.encode(payload, None, algorithm="hs256")
tests/test_api_jws.py:164:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = , payload = b'hello world'
key = None, algorithm = 'hs256', headers = None, json_encoder = None
is_payload_detached = False, sort_headers = False
def encode(self, payload: bytes, key: str | bytes | AllowedPrivateKeys | None=None, algorithm: str | None=None, headers: dict[str, Any] | None=None, json_encoder: type[json.JSONEncoder] | None=None, is_payload_detached: bool=False, sort_headers: bool=False) -> str:
"""Creates a JWT using the given algorithm.
Args:
payload: The claims content to sign
key: The key to use for signing the claim. Note: if the algorithm is None, the key is not used
algorithm: The signing algorithm to use. If none is specified then 'none' is used.
headers: A dict of additional headers to use.
json_encoder: A custom JSON encoder to use for encoding the JWT.
is_payload_detached: If True, the payload will be detached from the JWS.
sort_headers: If True, sort the header keys.
"""
# Check that we have a mapping
if not isinstance(payload, bytes):
raise TypeError('Payload must be bytes')
if algorithm is None:
algorithm = 'none'
if algorithm not in self._valid_algs:
> raise InvalidAlgorithmError('Algorithm not supported')
E jwt.exceptions.InvalidAlgorithmError: Algorithm not supported
jwt/api_jws.py:124: InvalidAlgorithmError
test_api_jws.py::TestJWS::test_encode_with_headers_alg_none
self =
jws = , payload = b'hello world'
def test_encode_with_headers_alg_none(self, jws, payload):
msg = jws.encode(payload, key=None, headers={"alg": "none"})
with pytest.raises(DecodeError) as context:
jws.decode(msg, algorithms=["none"])
> assert str(context.value) == "Signature verification failed"
E assert 'Algorithm "none" not allowed' == 'Signature ve...cation failed'
E
E - Signature verification failed
E + Algorithm "none" not allowed
tests/test_api_jws.py:173: AssertionError
test_api_jws.py::TestJWS::test_encode_with_headers_alg_es256
self =
jwt = b'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.aGVsbG8gd29ybGQ.'
key =
algorithms = ['ES256'], options = {}, detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}
signing_input = b'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.aGVsbG8gd29ybGQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
raise InvalidAlgorithmError('The specified alg value is not allowed')
if alg == 'none':
if merged_options['verify_signature']:
raise DecodeError('Algorithm "none" not allowed')
if key not in [None, '', 'none']:
raise InvalidKeyError('When alg = "none", key must be empty or "none"')
if signature != b'':
raise InvalidSignatureError('Signature verification failed')
return {
'header': header,
'payload': payload,
'signature': signature
}
try:
alg_obj = self._algorithms[alg]
except KeyError:
raise InvalidAlgorithmError('Algorithm not supported')
if merged_options['verify_signature']:
try:
if key is None:
raise InvalidKeyError('Key is required when algorithm is not "none"')
key = alg_obj.prepare_key(key)
except InvalidKeyError:
raise
except Exception as e:
raise InvalidTokenError('Unable to parse signature key: %s' % e)
try:
if not alg_obj.verify(signing_input if header.get('b64', True) else payload, key, signature):
> raise InvalidSignatureError('Signature verification failed')
E jwt.exceptions.InvalidSignatureError: Signature verification failed
jwt/api_jws.py:310: InvalidSignatureError
During handling of the above exception, another exception occurred:
self =
jws = , payload = b'hello world'
@crypto_required
def test_encode_with_headers_alg_es256(self, jws, payload):
with open(key_path("testkey_ec.priv"), "rb") as ec_priv_file:
priv_key = load_pem_private_key(ec_priv_file.read(), password=None)
with open(key_path("testkey_ec.pub"), "rb") as ec_pub_file:
pub_key = load_pem_public_key(ec_pub_file.read())
msg = jws.encode(payload, priv_key, headers={"alg": "ES256"})
> assert b"hello world" == jws.decode(msg, pub_key, algorithms=["ES256"])
tests/test_api_jws.py:183:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.aGVsbG8gd29ybGQ.'
key =
algorithms = ['ES256'], options = {}, detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}
signing_input = b'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.aGVsbG8gd29ybGQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
raise InvalidAlgorithmError('The specified alg value is not allowed')
if alg == 'none':
if merged_options['verify_signature']:
raise DecodeError('Algorithm "none" not allowed')
if key not in [None, '', 'none']:
raise InvalidKeyError('When alg = "none", key must be empty or "none"')
if signature != b'':
raise InvalidSignatureError('Signature verification failed')
return {
'header': header,
'payload': payload,
'signature': signature
}
try:
alg_obj = self._algorithms[alg]
except KeyError:
raise InvalidAlgorithmError('Algorithm not supported')
if merged_options['verify_signature']:
try:
if key is None:
raise InvalidKeyError('Key is required when algorithm is not "none"')
key = alg_obj.prepare_key(key)
except InvalidKeyError:
raise
except Exception as e:
raise InvalidTokenError('Unable to parse signature key: %s' % e)
try:
if not alg_obj.verify(signing_input if header.get('b64', True) else payload, key, signature):
raise InvalidSignatureError('Signature verification failed')
except Exception as e:
> raise InvalidSignatureError('Signature verification failed: %s' % e)
E jwt.exceptions.InvalidSignatureError: Signature verification failed: Signature verification failed
jwt/api_jws.py:312: InvalidSignatureError
test_api_jws.py::TestJWS::test_encode_with_alg_hs256_and_headers_alg_es256
test_api_jws.py::TestJWS::test_encode_with_alg_hs256_and_headers_alg_es256
self = , payload = b'hello world'
key =
algorithm = 'HS256', headers = {'alg': 'ES256'}, json_encoder = None
is_payload_detached = False, sort_headers = False
def encode(self, payload: bytes, key: str | bytes | AllowedPrivateKeys | None=None, algorithm: str | None=None, headers: dict[str, Any] | None=None, json_encoder: type[json.JSONEncoder] | None=None, is_payload_detached: bool=False, sort_headers: bool=False) -> str:
"""Creates a JWT using the given algorithm.
Args:
payload: The claims content to sign
key: The key to use for signing the claim. Note: if the algorithm is None, the key is not used
algorithm: The signing algorithm to use. If none is specified then 'none' is used.
headers: A dict of additional headers to use.
json_encoder: A custom JSON encoder to use for encoding the JWT.
is_payload_detached: If True, the payload will be detached from the JWS.
sort_headers: If True, sort the header keys.
"""
# Check that we have a mapping
if not isinstance(payload, bytes):
raise TypeError('Payload must be bytes')
if algorithm is None:
algorithm = 'none'
if algorithm not in self._valid_algs:
raise InvalidAlgorithmError('Algorithm not supported')
if algorithm != 'none' and key is None:
raise InvalidKeyError('Key is required when algorithm is not "none"')
# Header
header = {'alg': algorithm}
if self.header_typ is not None and 'typ' not in (headers or {}):
header['typ'] = self.header_typ
if headers:
header.update(headers)
if header.get('typ') == '':
del header['typ']
elif header.get('typ') is None:
del header['typ']
if is_payload_detached:
header['b64'] = False
if not payload:
raise InvalidTokenError('Payload cannot be empty when using detached content')
if sort_headers:
header = dict(sorted(header.items()))
json_header = json.dumps(header, separators=(',', ':'), cls=json_encoder).encode('utf-8')
header_input = base64url_encode(json_header)
if is_payload_detached:
payload_input = b''
else:
payload_input = base64url_encode(payload)
signing_input = b'.'.join([header_input, payload_input])
try:
alg_obj = self._algorithms[algorithm]
if algorithm == 'none':
key = None
elif key is None:
raise TypeError('Key is required when algorithm is not "none"')
else:
> key = alg_obj.prepare_key(key)
jwt/api_jws.py:166:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
key =
def prepare_key(self, key: Union[str, bytes]) -> bytes:
if not isinstance(key, (str, bytes)):
> raise TypeError('Expected a string value')
E TypeError: Expected a string value
jwt/algorithms.py:165: TypeError
During handling of the above exception, another exception occurred:
self =
jws = , payload = b'hello world'
@crypto_required
def test_encode_with_alg_hs256_and_headers_alg_es256(self, jws, payload):
with open(key_path("testkey_ec.priv"), "rb") as ec_priv_file:
priv_key = load_pem_private_key(ec_priv_file.read(), password=None)
with open(key_path("testkey_ec.pub"), "rb") as ec_pub_file:
pub_key = load_pem_public_key(ec_pub_file.read())
> msg = jws.encode(payload, priv_key, algorithm="HS256", headers={"alg": "ES256"})
tests/test_api_jws.py:192:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = , payload = b'hello world'
key =
algorithm = 'HS256', headers = {'alg': 'ES256'}, json_encoder = None
is_payload_detached = False, sort_headers = False
def encode(self, payload: bytes, key: str | bytes | AllowedPrivateKeys | None=None, algorithm: str | None=None, headers: dict[str, Any] | None=None, json_encoder: type[json.JSONEncoder] | None=None, is_payload_detached: bool=False, sort_headers: bool=False) -> str:
"""Creates a JWT using the given algorithm.
Args:
payload: The claims content to sign
key: The key to use for signing the claim. Note: if the algorithm is None, the key is not used
algorithm: The signing algorithm to use. If none is specified then 'none' is used.
headers: A dict of additional headers to use.
json_encoder: A custom JSON encoder to use for encoding the JWT.
is_payload_detached: If True, the payload will be detached from the JWS.
sort_headers: If True, sort the header keys.
"""
# Check that we have a mapping
if not isinstance(payload, bytes):
raise TypeError('Payload must be bytes')
if algorithm is None:
algorithm = 'none'
if algorithm not in self._valid_algs:
raise InvalidAlgorithmError('Algorithm not supported')
if algorithm != 'none' and key is None:
raise InvalidKeyError('Key is required when algorithm is not "none"')
# Header
header = {'alg': algorithm}
if self.header_typ is not None and 'typ' not in (headers or {}):
header['typ'] = self.header_typ
if headers:
header.update(headers)
if header.get('typ') == '':
del header['typ']
elif header.get('typ') is None:
del header['typ']
if is_payload_detached:
header['b64'] = False
if not payload:
raise InvalidTokenError('Payload cannot be empty when using detached content')
if sort_headers:
header = dict(sorted(header.items()))
json_header = json.dumps(header, separators=(',', ':'), cls=json_encoder).encode('utf-8')
header_input = base64url_encode(json_header)
if is_payload_detached:
payload_input = b''
else:
payload_input = base64url_encode(payload)
signing_input = b'.'.join([header_input, payload_input])
try:
alg_obj = self._algorithms[algorithm]
if algorithm == 'none':
key = None
elif key is None:
raise TypeError('Key is required when algorithm is not "none"')
else:
key = alg_obj.prepare_key(key)
signature = alg_obj.sign(signing_input if not is_payload_detached else payload, key)
except Exception as e:
> raise TypeError('Unable to encode JWT: %s' % e)
E TypeError: Unable to encode JWT: Expected a string value
jwt/api_jws.py:169: TypeError
test_api_jws.py::TestJWS::test_bad_secret
test_api_jws.py::TestJWS::test_bad_secret
self =
jws = , payload = b'hello world'
def test_bad_secret(self, jws, payload):
right_secret = "foo"
bad_secret = "bar"
jws_message = jws.encode(payload, right_secret)
with pytest.raises(DecodeError) as excinfo:
# Backward compat for ticket #315
> jws.decode(jws_message, bad_secret, algorithms=["HS256"])
tests/test_api_jws.py:215:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ.', key = 'bar'
algorithms = ['HS256'], options = {}, detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jws.py::TestJWS::test_decode_with_optional_algorithms
test_api_jws.py::TestJWS::test_decode_with_optional_algorithms
self =
jws =
def test_decode_with_optional_algorithms(self, jws):
example_secret = "secret"
example_jws = (
b"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
b"aGVsbG8gd29ybGQ."
b"SIr03zM64awWRdPrAM_61QWsZchAtgDV3pphfHPPWkI"
)
> with pytest.raises(DecodeError) as exc:
E Failed: DID NOT RAISE
tests/test_api_jws.py:326: Failed
test_api_jws.py::TestJWS::test_load_no_verification
test_api_jws.py::TestJWS::test_load_no_verification
self =
jws = , payload = b'hello world'
def test_load_no_verification(self, jws, payload):
right_secret = "foo"
jws_message = jws.encode(payload, right_secret)
> decoded_payload = jws.decode(
jws_message,
key=None,
algorithms=["HS256"],
options={"verify_signature": False},
)
tests/test_api_jws.py:352:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ.', key = None
algorithms = ['HS256'], options = {'verify_signature': False}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': False}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jws.py::TestJWS::test_no_secret
test_api_jws.py::TestJWS::test_no_secret
self =
jws = , payload = b'hello world'
def test_no_secret(self, jws, payload):
right_secret = "foo"
jws_message = jws.encode(payload, right_secret)
with pytest.raises(DecodeError):
> jws.decode(jws_message, algorithms=["HS256"])
tests/test_api_jws.py:366:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ.', key = None
algorithms = ['HS256'], options = {}, detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jws.py::TestJWS::test_verify_signature_with_no_secret
test_api_jws.py::TestJWS::test_verify_signature_with_no_secret
self =
jws = , payload = b'hello world'
def test_verify_signature_with_no_secret(self, jws, payload):
right_secret = "foo"
jws_message = jws.encode(payload, right_secret)
with pytest.raises(DecodeError) as exc:
> jws.decode(jws_message, algorithms=["HS256"])
tests/test_api_jws.py:373:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ.', key = None
algorithms = ['HS256'], options = {}, detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jws.py::TestJWS::test_verify_signature_with_no_algo_header_throws_exception
self =
jwt = b'e30.eyJhIjo1fQ.KEh186CjVw_Q8FadjJcaVnE7hO5Z9nHBbU8TgbhHcBY'
key = 'secret', algorithms = ['HS256'], options = {}, detached_payload = None
kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}, signing_input = b'e30.eyJhIjo1fQ'
crypto_segment = b'KEh186CjVw_Q8FadjJcaVnE7hO5Z9nHBbU8TgbhHcBY'
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
> alg = header['alg']
E KeyError: 'alg'
jwt/api_jws.py:273: KeyError
During handling of the above exception, another exception occurred:
self =
jws = , payload = b'hello world'
def test_verify_signature_with_no_algo_header_throws_exception(self, jws, payload):
example_jws = b"e30.eyJhIjo1fQ.KEh186CjVw_Q8FadjJcaVnE7hO5Z9nHBbU8TgbhHcBY"
with pytest.raises(InvalidAlgorithmError):
> jws.decode(example_jws, "secret", algorithms=["HS256"])
tests/test_api_jws.py:381:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'e30.eyJhIjo1fQ.KEh186CjVw_Q8FadjJcaVnE7hO5Z9nHBbU8TgbhHcBY'
key = 'secret', algorithms = ['HS256'], options = {}, detached_payload = None
kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}, signing_input = b'e30.eyJhIjo1fQ'
crypto_segment = b'KEh186CjVw_Q8FadjJcaVnE7hO5Z9nHBbU8TgbhHcBY'
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
> raise InvalidTokenError('Missing algorithm ("alg") in headers')
E jwt.exceptions.InvalidTokenError: Missing algorithm ("alg") in headers
jwt/api_jws.py:275: InvalidTokenError
test_api_jws.py::TestJWS::test_invalid_crypto_alg
test_api_jws.py::TestJWS::test_invalid_crypto_alg
self =
jws = , payload = b'hello world'
def test_invalid_crypto_alg(self, jws, payload):
with pytest.raises(NotImplementedError):
> jws.encode(payload, "secret", algorithm="HS1024")
tests/test_api_jws.py:385:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = , payload = b'hello world'
key = 'secret', algorithm = 'HS1024', headers = None, json_encoder = None
is_payload_detached = False, sort_headers = False
def encode(self, payload: bytes, key: str | bytes | AllowedPrivateKeys | None=None, algorithm: str | None=None, headers: dict[str, Any] | None=None, json_encoder: type[json.JSONEncoder] | None=None, is_payload_detached: bool=False, sort_headers: bool=False) -> str:
"""Creates a JWT using the given algorithm.
Args:
payload: The claims content to sign
key: The key to use for signing the claim. Note: if the algorithm is None, the key is not used
algorithm: The signing algorithm to use. If none is specified then 'none' is used.
headers: A dict of additional headers to use.
json_encoder: A custom JSON encoder to use for encoding the JWT.
is_payload_detached: If True, the payload will be detached from the JWS.
sort_headers: If True, sort the header keys.
"""
# Check that we have a mapping
if not isinstance(payload, bytes):
raise TypeError('Payload must be bytes')
if algorithm is None:
algorithm = 'none'
if algorithm not in self._valid_algs:
> raise InvalidAlgorithmError('Algorithm not supported')
E jwt.exceptions.InvalidAlgorithmError: Algorithm not supported
jwt/api_jws.py:124: InvalidAlgorithmError
test_api_jws.py::TestJWS::test_unicode_secret
test_api_jws.py::TestJWS::test_unicode_secret
self =
jws = , payload = b'hello world'
def test_unicode_secret(self, jws, payload):
secret = "\xc2"
jws_message = jws.encode(payload, secret)
> decoded_payload = jws.decode(jws_message, secret, algorithms=["HS256"])
tests/test_api_jws.py:396:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ.', key = 'Â'
algorithms = ['HS256'], options = {}, detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jws.py::TestJWS::test_nonascii_secret
test_api_jws.py::TestJWS::test_nonascii_secret
self =
jws = , payload = b'hello world'
def test_nonascii_secret(self, jws, payload):
secret = "\xc2" # char value that ascii codec cannot decode
jws_message = jws.encode(payload, secret)
> decoded_payload = jws.decode(jws_message, secret, algorithms=["HS256"])
tests/test_api_jws.py:404:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ.', key = 'Â'
algorithms = ['HS256'], options = {}, detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jws.py::TestJWS::test_bytes_secret
test_api_jws.py::TestJWS::test_bytes_secret
self =
jws = , payload = b'hello world'
def test_bytes_secret(self, jws, payload):
secret = b"\xc2" # char value that ascii codec cannot decode
jws_message = jws.encode(payload, secret)
> decoded_payload = jws.decode(jws_message, secret, algorithms=["HS256"])
tests/test_api_jws.py:412:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jws.py:346: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ.', key = b'\xc2'
algorithms = ['HS256'], options = {}, detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'verify_signature': True}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.aGVsbG8gd29ybGQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jws.py::TestJWS::test_get_unverified_header_fails_on_bad_header_types
self =
jws = , payload = b'hello world'
def test_get_unverified_header_fails_on_bad_header_types(self, jws, payload):
# Contains a bad kid value (int 123 instead of string)
example_jws = (
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6MTIzfQ"
".eyJzdWIiOiIxMjM0NTY3ODkwIn0"
".vs2WY54jfpKP3JGC73Vq5YlMsqM5oTZ1ZydT77SiZSk"
)
> with pytest.raises(InvalidTokenError) as exc:
E Failed: DID NOT RAISE
tests/test_api_jws.py:510: Failed
test_api_jws.py::TestJWS::test_encode_decode_ecdsa_related_algorithms[ES256K]
self =
jws = , payload = b'hello world'
algo = 'ES256K'
@pytest.mark.parametrize(
"algo",
[
"ES256",
"ES256K",
"ES384",
"ES512",
],
)
@crypto_required
def test_encode_decode_ecdsa_related_algorithms(self, jws, payload, algo):
# PEM-formatted EC key
with open(key_path("testkey_ec.priv"), "rb") as ec_priv_file:
priv_eckey = load_pem_private_key(ec_priv_file.read(), password=None)
> jws_message = jws.encode(payload, priv_eckey, algorithm=algo)
tests/test_api_jws.py:577:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = , payload = b'hello world'
key =
algorithm = 'ES256K', headers = None, json_encoder = None
is_payload_detached = False, sort_headers = False
def encode(self, payload: bytes, key: str | bytes | AllowedPrivateKeys | None=None, algorithm: str | None=None, headers: dict[str, Any] | None=None, json_encoder: type[json.JSONEncoder] | None=None, is_payload_detached: bool=False, sort_headers: bool=False) -> str:
"""Creates a JWT using the given algorithm.
Args:
payload: The claims content to sign
key: The key to use for signing the claim. Note: if the algorithm is None, the key is not used
algorithm: The signing algorithm to use. If none is specified then 'none' is used.
headers: A dict of additional headers to use.
json_encoder: A custom JSON encoder to use for encoding the JWT.
is_payload_detached: If True, the payload will be detached from the JWS.
sort_headers: If True, sort the header keys.
"""
# Check that we have a mapping
if not isinstance(payload, bytes):
raise TypeError('Payload must be bytes')
if algorithm is None:
algorithm = 'none'
if algorithm not in self._valid_algs:
> raise InvalidAlgorithmError('Algorithm not supported')
E jwt.exceptions.InvalidAlgorithmError: Algorithm not supported
jwt/api_jws.py:124: InvalidAlgorithmError
test_api_jws.py::TestJWS::test_ecdsa_related_algorithms
self =
jws =
def test_ecdsa_related_algorithms(self, jws):
jws = PyJWS()
jws_algorithms = jws.get_algorithms()
if has_crypto:
assert "ES256" in jws_algorithms
> assert "ES256K" in jws_algorithms
E AssertionError: assert 'ES256K' in ['ES384', 'RS384', 'EdDSA', 'HS256', 'RS256', 'RS512', ...]
tests/test_api_jws.py:598: AssertionError
test_api_jws.py::TestJWS::test_encode_fails_on_invalid_kid_types
test_api_jws.py::TestJWS::test_encode_fails_on_invalid_kid_types
self =
jws = , payload = b'hello world'
def test_encode_fails_on_invalid_kid_types(self, jws, payload):
> with pytest.raises(InvalidTokenError) as exc:
E Failed: DID NOT RAISE
tests/test_api_jws.py:724: Failed
test_api_jws.py::TestJWS::test_encode_detached_content_with_b64_header
self =
jws = , payload = b'hello world'
def test_encode_detached_content_with_b64_header(self, jws, payload):
secret = "secret"
# Check that detached content is automatically detected when b64 is false
headers = {"b64": False}
token = jws.encode(payload, secret, "HS256", headers)
msg_header, msg_payload, _ = token.split(".")
msg_header = base64url_decode(msg_header.encode())
msg_header_obj = json.loads(msg_header)
assert "b64" in msg_header_obj
assert msg_header_obj["b64"] is False
# Check that the payload is not inside the token
> assert not msg_payload
E AssertionError: assert not 'aGVsbG8gd29ybGQ'
tests/test_api_jws.py:756: AssertionError
test_api_jws.py::TestJWS::test_decode_detached_content_without_proper_argument
test_api_jws.py::TestJWS::test_decode_detached_content_without_proper_argument
self =
jws =
def test_decode_detached_content_without_proper_argument(self, jws):
example_jws = (
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2V9"
"."
".65yNkX_ZH4A_6pHaTL_eI84OXOHtfl4K0k5UnlXZ8f4"
)
example_secret = "secret"
with pytest.raises(DecodeError) as exc:
jws.decode(example_jws, example_secret, algorithms=["HS256"])
> assert (
'It is required that you pass in a value for the "detached_payload" argument to decode a message having the b64 header set to false.'
in str(exc.value)
)
E assert 'It is required that you pass in a value for the "detached_payload" argument to decode a message having the b64 header set to false.' in 'It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.'
E + where 'It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.' = str(DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.'))
E + where DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.') = .value
tests/test_api_jws.py:780: AssertionError
test_api_jwt.py::TestJWT::test_decode_with_invalid_audience_param_throws_exception
test_api_jwt.py::TestJWT::test_decode_with_invalid_audience_param_throws_exception
self =
jwt =
def test_decode_with_invalid_audience_param_throws_exception(self, jwt):
secret = "secret"
example_jwt = (
"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9"
".eyJoZWxsbyI6ICJ3b3JsZCJ9"
".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8"
)
with pytest.raises(TypeError) as context:
> jwt.decode(example_jwt, secret, audience=1, algorithms=["HS256"])
tests/test_api_jwt.py:119:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
key = 'secret', algorithms = ['HS256'], options = None, kwargs = {'audience': 1}
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
decoded = {'header': {'alg': 'HS256', 'typ': 'JWT'}, 'payload': b'{"hello": "world"}', 'signature': b'\xb6\xf6\xa0,2\xe8j"J\xc4\xe2\xaa\xa4\x15\xd2\x10l\xbbI\x84\xa2}\x98c\x9e\xd8&\xf5\xcbi\xca?'}
payload = {'hello': 'world'}
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, **kwargs: Any) -> dict[str, Any]:
"""
Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
merged_options = {**self.options, **(options or {})}
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
payload = self._decode_payload(decoded)
if merged_options['verify_exp'] and 'exp' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
exp = datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
leeway = timedelta(seconds=kwargs.get('leeway', 0))
if now > exp + leeway:
raise ExpiredSignatureError('Signature has expired')
if merged_options['verify_nbf'] and 'nbf' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
nbf = datetime.fromtimestamp(payload['nbf'], tz=timezone.utc)
leeway = timedelta(seconds=kwargs.get('leeway', 0))
if now < nbf - leeway:
raise ImmatureSignatureError('The token is not yet valid (nbf)')
if merged_options['verify_iat'] and 'iat' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
iat = datetime.fromtimestamp(payload['iat'], tz=timezone.utc)
leeway = timedelta(seconds=kwargs.get('leeway', 0))
if now < iat - leeway:
raise InvalidIssuedAtError('Issued at claim (iat) cannot be in the future')
if merged_options['verify_iss']:
expected_issuer = kwargs.get('issuer', None)
if expected_issuer is not None:
if 'iss' not in payload:
raise MissingRequiredClaimError('Issuer claim expected but not present')
if payload['iss'] != expected_issuer:
raise InvalidIssuerError('Invalid issuer')
if merged_options['verify_aud']:
expected_audience = kwargs.get('audience', None)
if expected_audience is not None:
if 'aud' not in payload:
> raise MissingRequiredClaimError('Audience claim expected but not present')
E jwt.exceptions.MissingRequiredClaimError: Token is missing the "Audience claim expected but not present" claim
jwt/api_jwt.py:147: MissingRequiredClaimError
test_api_jwt.py::TestJWT::test_decode_raises_exception_if_exp_is_not_int
test_api_jwt.py::TestJWT::test_decode_raises_exception_if_exp_is_not_int
self =
jwt =
def test_decode_raises_exception_if_exp_is_not_int(self, jwt):
# >>> jwt.encode({'exp': 'not-an-int'}, 'secret')
example_jwt = (
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
"eyJleHAiOiJub3QtYW4taW50In0."
"P65iYgoHtBqB07PMtBSuKNUEIPPPfmjfJG217cEE66s"
)
with pytest.raises(DecodeError) as exc:
> jwt.decode(example_jwt, "secret", algorithms=["HS256"])
tests/test_api_jwt.py:206:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiJub3QtYW4taW50In0.P65iYgoHtBqB07PMtBSuKNUEIPPPfmjfJG217cEE66s'
key = 'secret', algorithms = ['HS256'], options = None, kwargs = {}
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
decoded = {'header': {'alg': 'HS256', 'typ': 'JWT'}, 'payload': b'{"exp":"not-an-int"}', 'signature': b'?\xaebb\n\x07\xb4\x1a\x81\xd3\xb3\xcc\xb4\x14\xae(\xd5\x04 \xf3\xcf~h\xdf$m\xb5\xed\xc1\x04\xeb\xab'}
payload = {'exp': 'not-an-int'}
now = datetime.datetime(2024, 11, 29, 4, 31, 42, 942554, tzinfo=datetime.timezone.utc)
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, **kwargs: Any) -> dict[str, Any]:
"""
Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
merged_options = {**self.options, **(options or {})}
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
payload = self._decode_payload(decoded)
if merged_options['verify_exp'] and 'exp' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
> exp = datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
E TypeError: 'str' object cannot be interpreted as an integer
jwt/api_jwt.py:116: TypeError
test_api_jwt.py::TestJWT::test_decode_raises_exception_if_iat_is_not_int
test_api_jwt.py::TestJWT::test_decode_raises_exception_if_iat_is_not_int
self =
jwt =
def test_decode_raises_exception_if_iat_is_not_int(self, jwt):
# >>> jwt.encode({'iat': 'not-an-int'}, 'secret')
example_jwt = (
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
"eyJpYXQiOiJub3QtYW4taW50In0."
"H1GmcQgSySa5LOKYbzGm--b1OmRbHFkyk8pq811FzZM"
)
with pytest.raises(InvalidIssuedAtError):
> jwt.decode(example_jwt, "secret", algorithms=["HS256"])
tests/test_api_jwt.py:219:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiJub3QtYW4taW50In0.H1GmcQgSySa5LOKYbzGm--b1OmRbHFkyk8pq811FzZM'
key = 'secret', algorithms = ['HS256'], options = None, kwargs = {}
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
decoded = {'header': {'alg': 'HS256', 'typ': 'JWT'}, 'payload': b'{"iat":"not-an-int"}', 'signature': b'\x1fQ\xa6q\x08\x12\xc9&\xb9,\xe2\x98o1\xa6\xfb\xe6\xf5:d[\x1cY2\x93\xcaj\xf3]E\xcd\x93'}
payload = {'iat': 'not-an-int'}
now = datetime.datetime(2024, 11, 29, 4, 31, 42, 964811, tzinfo=datetime.timezone.utc)
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, **kwargs: Any) -> dict[str, Any]:
"""
Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
merged_options = {**self.options, **(options or {})}
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
payload = self._decode_payload(decoded)
if merged_options['verify_exp'] and 'exp' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
exp = datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
leeway = timedelta(seconds=kwargs.get('leeway', 0))
if now > exp + leeway:
raise ExpiredSignatureError('Signature has expired')
if merged_options['verify_nbf'] and 'nbf' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
nbf = datetime.fromtimestamp(payload['nbf'], tz=timezone.utc)
leeway = timedelta(seconds=kwargs.get('leeway', 0))
if now < nbf - leeway:
raise ImmatureSignatureError('The token is not yet valid (nbf)')
if merged_options['verify_iat'] and 'iat' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
> iat = datetime.fromtimestamp(payload['iat'], tz=timezone.utc)
E TypeError: 'str' object cannot be interpreted as an integer
jwt/api_jwt.py:130: TypeError
test_api_jwt.py::TestJWT::test_decode_raises_exception_if_iat_is_greater_than_now
test_api_jwt.py::TestJWT::test_decode_raises_exception_if_iat_is_greater_than_now
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854717, 'iat': 1732854712, 'iss': 'jeff'}
def test_decode_raises_exception_if_iat_is_greater_than_now(self, jwt, payload):
payload["iat"] = utc_timestamp() + 10
secret = "secret"
jwt_message = jwt.encode(payload, secret)
with pytest.raises(ImmatureSignatureError):
> jwt.decode(jwt_message, secret, algorithms=["HS256"])
tests/test_api_jwt.py:227:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE3LCJjbGFpbSI6Imluc2FuaXR5IiwiaWF0IjoxNzMyODU0NzEyfQ.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE3LCJjbGFpbSI6Imluc2FuaXR5IiwiaWF0IjoxNzMyODU0NzEyfQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_works_if_iat_is_str_of_a_number
test_api_jwt.py::TestJWT::test_decode_works_if_iat_is_str_of_a_number
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iat': 1638202770, 'iss': 'jeff'}
def test_decode_works_if_iat_is_str_of_a_number(self, jwt, payload):
payload["iat"] = "1638202770"
secret = "secret"
jwt_message = jwt.encode(payload, secret)
> data = jwt.decode(jwt_message, secret, algorithms=["HS256"])
tests/test_api_jwt.py:233:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5IiwiaWF0IjoxNjM4MjAyNzcwfQ.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5IiwiaWF0IjoxNjM4MjAyNzcwfQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_raises_exception_if_nbf_is_not_int
test_api_jwt.py::TestJWT::test_decode_raises_exception_if_nbf_is_not_int
self =
jwt =
def test_decode_raises_exception_if_nbf_is_not_int(self, jwt):
# >>> jwt.encode({'nbf': 'not-an-int'}, 'secret')
example_jwt = (
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
"eyJuYmYiOiJub3QtYW4taW50In0."
"c25hldC8G2ZamC8uKpax9sYMTgdZo3cxrmzFHaAAluw"
)
with pytest.raises(DecodeError):
> jwt.decode(example_jwt, "secret", algorithms=["HS256"])
tests/test_api_jwt.py:245:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOiJub3QtYW4taW50In0.c25hldC8G2ZamC8uKpax9sYMTgdZo3cxrmzFHaAAluw'
key = 'secret', algorithms = ['HS256'], options = None, kwargs = {}
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
decoded = {'header': {'alg': 'HS256', 'typ': 'JWT'}, 'payload': b'{"nbf":"not-an-int"}', 'signature': b'sna\x95\xd0\xbc\x1bfZ\x98/.*\x96\xb1\xf6\xc6\x0cN\x07Y\xa3w1\xael\xc5\x1d\xa0\x00\x96\xec'}
payload = {'nbf': 'not-an-int'}
now = datetime.datetime(2024, 11, 29, 4, 31, 43, 36709, tzinfo=datetime.timezone.utc)
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, **kwargs: Any) -> dict[str, Any]:
"""
Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
merged_options = {**self.options, **(options or {})}
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
payload = self._decode_payload(decoded)
if merged_options['verify_exp'] and 'exp' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
exp = datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
leeway = timedelta(seconds=kwargs.get('leeway', 0))
if now > exp + leeway:
raise ExpiredSignatureError('Signature has expired')
if merged_options['verify_nbf'] and 'nbf' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
> nbf = datetime.fromtimestamp(payload['nbf'], tz=timezone.utc)
E TypeError: 'str' object cannot be interpreted as an integer
jwt/api_jwt.py:123: TypeError
test_api_jwt.py::TestJWT::test_encode_datetime
test_api_jwt.py::TestJWT::test_encode_datetime
self =
jwt =
def test_encode_datetime(self, jwt):
secret = "secret"
current_datetime = datetime.now(tz=timezone.utc)
payload = {
"exp": current_datetime,
"iat": current_datetime,
"nbf": current_datetime,
}
jwt_message = jwt.encode(payload, secret)
> decoded_payload = jwt.decode(
jwt_message, secret, leeway=1, algorithms=["HS256"]
)
tests/test_api_jwt.py:266:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOjE3MzI4NTQ3MDMsImlhdCI6MTczMjg1NDcwMywibmJmIjoxNzMyODU0NzAzfQ.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOjE3MzI4NTQ3MDMsImlhdCI6MTczMjg1NDcwMywibmJmIjoxNzMyODU0NzAzfQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_with_expiration
test_api_jwt.py::TestJWT::test_decode_with_expiration
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854702, 'iss': 'jeff'}
def test_decode_with_expiration(self, jwt, payload):
payload["exp"] = utc_timestamp() - 1
secret = "secret"
jwt_message = jwt.encode(payload, secret)
with pytest.raises(ExpiredSignatureError):
> jwt.decode(jwt_message, secret, algorithms=["HS256"])
tests/test_api_jwt.py:329:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzAyLCJjbGFpbSI6Imluc2FuaXR5In0.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzAyLCJjbGFpbSI6Imluc2FuaXR5In0'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_with_notbefore
test_api_jwt.py::TestJWT::test_decode_with_notbefore
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iss': 'jeff', 'nbf': 1732854713}
def test_decode_with_notbefore(self, jwt, payload):
payload["nbf"] = utc_timestamp() + 10
secret = "secret"
jwt_message = jwt.encode(payload, secret)
with pytest.raises(ImmatureSignatureError):
> jwt.decode(jwt_message, secret, algorithms=["HS256"])
tests/test_api_jwt.py:337:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5IiwibmJmIjoxNzMyODU0NzEzfQ.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5IiwibmJmIjoxNzMyODU0NzEzfQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_skip_expiration_verification
test_api_jwt.py::TestJWT::test_decode_skip_expiration_verification
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854702.144174, 'iss': 'jeff'}
def test_decode_skip_expiration_verification(self, jwt, payload):
payload["exp"] = time.time() - 1
secret = "secret"
jwt_message = jwt.encode(payload, secret)
> jwt.decode(
jwt_message,
secret,
algorithms=["HS256"],
options={"verify_exp": False},
)
tests/test_api_jwt.py:344:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzAyLjE0NDE3NCwiY2xhaW0iOiJpbnNhbml0eSJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': False, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': False, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzAyLjE0NDE3NCwiY2xhaW0iOiJpbnNhbml0eSJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_skip_notbefore_verification
test_api_jwt.py::TestJWT::test_decode_skip_notbefore_verification
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iss': 'jeff', 'nbf': 1732854713.169743}
def test_decode_skip_notbefore_verification(self, jwt, payload):
payload["nbf"] = time.time() + 10
secret = "secret"
jwt_message = jwt.encode(payload, secret)
> jwt.decode(
jwt_message,
secret,
algorithms=["HS256"],
options={"verify_nbf": False},
)
tests/test_api_jwt.py:356:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5IiwibmJmIjoxNzMyODU0NzEzLjE2OTc0M30.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5IiwibmJmIjoxNzMyODU0NzEzLjE2OTc0M30'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_with_expiration_with_leeway
test_api_jwt.py::TestJWT::test_decode_with_expiration_with_leeway
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854701, 'iss': 'jeff'}
def test_decode_with_expiration_with_leeway(self, jwt, payload):
payload["exp"] = utc_timestamp() - 2
secret = "secret"
jwt_message = jwt.encode(payload, secret)
# With 5 seconds leeway, should be ok
for leeway in (5, timedelta(seconds=5)):
> decoded = jwt.decode(
jwt_message, secret, leeway=leeway, algorithms=["HS256"]
)
tests/test_api_jwt.py:370:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzAxLCJjbGFpbSI6Imluc2FuaXR5In0.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzAxLCJjbGFpbSI6Imluc2FuaXR5In0'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_with_notbefore_with_leeway
test_api_jwt.py::TestJWT::test_decode_with_notbefore_with_leeway
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iss': 'jeff', 'nbf': 1732854713}
def test_decode_with_notbefore_with_leeway(self, jwt, payload):
payload["nbf"] = utc_timestamp() + 10
secret = "secret"
jwt_message = jwt.encode(payload, secret)
# With 13 seconds leeway, should be ok
> jwt.decode(jwt_message, secret, leeway=13, algorithms=["HS256"])
tests/test_api_jwt.py:386:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5IiwibmJmIjoxNzMyODU0NzEzfQ.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5IiwibmJmIjoxNzMyODU0NzEzfQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_check_audience_when_valid
test_api_jwt.py::TestJWT::test_check_audience_when_valid
self =
jwt =
def test_check_audience_when_valid(self, jwt):
payload = {"some": "payload", "aud": "urn:me"}
token = jwt.encode(payload, "secret")
> jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"])
tests/test_api_jwt.py:394:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjptZSJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjptZSJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_check_audience_list_when_valid
test_api_jwt.py::TestJWT::test_check_audience_list_when_valid
self =
jwt =
def test_check_audience_list_when_valid(self, jwt):
payload = {"some": "payload", "aud": "urn:me"}
token = jwt.encode(payload, "secret")
> jwt.decode(
token,
"secret",
audience=["urn:you", "urn:me"],
algorithms=["HS256"],
)
tests/test_api_jwt.py:399:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjptZSJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjptZSJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_check_audience_none_specified
test_api_jwt.py::TestJWT::test_check_audience_none_specified
self =
jwt =
def test_check_audience_none_specified(self, jwt):
payload = {"some": "payload", "aud": "urn:me"}
token = jwt.encode(payload, "secret")
with pytest.raises(InvalidAudienceError):
> jwt.decode(token, "secret", algorithms=["HS256"])
tests/test_api_jwt.py:410:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjptZSJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjptZSJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_raise_exception_invalid_audience_list
test_api_jwt.py::TestJWT::test_raise_exception_invalid_audience_list
self =
jwt =
def test_raise_exception_invalid_audience_list(self, jwt):
payload = {"some": "payload", "aud": "urn:me"}
token = jwt.encode(payload, "secret")
with pytest.raises(InvalidAudienceError):
> jwt.decode(
token,
"secret",
audience=["urn:you", "urn:him"],
algorithms=["HS256"],
)
tests/test_api_jwt.py:416:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjptZSJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjptZSJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_check_audience_in_array_when_valid
test_api_jwt.py::TestJWT::test_check_audience_in_array_when_valid
self =
jwt =
def test_check_audience_in_array_when_valid(self, jwt):
payload = {"some": "payload", "aud": ["urn:me", "urn:someone-else"]}
token = jwt.encode(payload, "secret")
> jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"])
tests/test_api_jwt.py:426:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6WyJ1cm46bWUiLCJ1cm46c29tZW9uZS1lbHNlIl19.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6WyJ1cm46bWUiLCJ1cm46c29tZW9uZS1lbHNlIl19'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_raise_exception_invalid_audience
test_api_jwt.py::TestJWT::test_raise_exception_invalid_audience
self =
jwt =
def test_raise_exception_invalid_audience(self, jwt):
payload = {"some": "payload", "aud": "urn:someone-else"}
token = jwt.encode(payload, "secret")
with pytest.raises(InvalidAudienceError):
> jwt.decode(token, "secret", audience="urn-me", algorithms=["HS256"])
tests/test_api_jwt.py:434:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjpzb21lb25lLWVsc2UifQ.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjpzb21lb25lLWVsc2UifQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_raise_exception_audience_as_bytes
test_api_jwt.py::TestJWT::test_raise_exception_audience_as_bytes
self =
jwt =
def test_raise_exception_audience_as_bytes(self, jwt):
payload = {"some": "payload", "aud": ["urn:me", "urn:someone-else"]}
token = jwt.encode(payload, "secret")
with pytest.raises(InvalidAudienceError):
> jwt.decode(
token, "secret", audience="urn:me".encode(), algorithms=["HS256"]
)
tests/test_api_jwt.py:440:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6WyJ1cm46bWUiLCJ1cm46c29tZW9uZS1lbHNlIl19.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6WyJ1cm46bWUiLCJ1cm46c29tZW9uZS1lbHNlIl19'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_raise_exception_invalid_audience_in_array
test_api_jwt.py::TestJWT::test_raise_exception_invalid_audience_in_array
self =
jwt =
def test_raise_exception_invalid_audience_in_array(self, jwt):
payload = {
"some": "payload",
"aud": ["urn:someone", "urn:someone-else"],
}
token = jwt.encode(payload, "secret")
with pytest.raises(InvalidAudienceError):
> jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"])
tests/test_api_jwt.py:453:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6WyJ1cm46c29tZW9uZSIsInVybjpzb21lb25lLWVsc2UiXX0.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6WyJ1cm46c29tZW9uZSIsInVybjpzb21lb25lLWVsc2UiXX0'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_raise_exception_token_without_issuer
test_api_jwt.py::TestJWT::test_raise_exception_token_without_issuer
self =
jwt =
def test_raise_exception_token_without_issuer(self, jwt):
issuer = "urn:wrong"
payload = {"some": "payload"}
token = jwt.encode(payload, "secret")
with pytest.raises(MissingRequiredClaimError) as exc:
> jwt.decode(token, "secret", issuer=issuer, algorithms=["HS256"])
tests/test_api_jwt.py:463:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_raise_exception_token_without_audience
test_api_jwt.py::TestJWT::test_raise_exception_token_without_audience
self =
jwt =
def test_raise_exception_token_without_audience(self, jwt):
payload = {"some": "payload"}
token = jwt.encode(payload, "secret")
with pytest.raises(MissingRequiredClaimError) as exc:
> jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"])
tests/test_api_jwt.py:472:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_raise_exception_token_with_aud_none_and_without_audience
test_api_jwt.py::TestJWT::test_raise_exception_token_with_aud_none_and_without_audience
self =
jwt =
def test_raise_exception_token_with_aud_none_and_without_audience(self, jwt):
payload = {"some": "payload", "aud": None}
token = jwt.encode(payload, "secret")
with pytest.raises(MissingRequiredClaimError) as exc:
> jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"])
tests/test_api_jwt.py:481:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6bnVsbH0.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6bnVsbH0'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_check_issuer_when_valid
test_api_jwt.py::TestJWT::test_check_issuer_when_valid
self =
jwt =
def test_check_issuer_when_valid(self, jwt):
issuer = "urn:foo"
payload = {"some": "payload", "iss": "urn:foo"}
token = jwt.encode(payload, "secret")
> jwt.decode(token, "secret", issuer=issuer, algorithms=["HS256"])
tests/test_api_jwt.py:489:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImlzcyI6InVybjpmb28ifQ.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImlzcyI6InVybjpmb28ifQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_raise_exception_invalid_issuer
test_api_jwt.py::TestJWT::test_raise_exception_invalid_issuer
self =
jwt =
def test_raise_exception_invalid_issuer(self, jwt):
issuer = "urn:wrong"
payload = {"some": "payload", "iss": "urn:foo"}
token = jwt.encode(payload, "secret")
with pytest.raises(InvalidIssuerError):
> jwt.decode(token, "secret", issuer=issuer, algorithms=["HS256"])
tests/test_api_jwt.py:499:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImlzcyI6InVybjpmb28ifQ.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImlzcyI6InVybjpmb28ifQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_skip_check_audience
test_api_jwt.py::TestJWT::test_skip_check_audience
self =
jwt =
def test_skip_check_audience(self, jwt):
payload = {"some": "payload", "aud": "urn:me"}
token = jwt.encode(payload, "secret")
> jwt.decode(
token,
"secret",
options={"verify_aud": False},
algorithms=["HS256"],
)
tests/test_api_jwt.py:504:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjptZSJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': False, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': False, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImF1ZCI6InVybjptZSJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_skip_check_exp
test_api_jwt.py::TestJWT::test_skip_check_exp
self =
jwt =
def test_skip_check_exp(self, jwt):
payload = {
"some": "payload",
"exp": datetime.now(tz=timezone.utc) - timedelta(days=1),
}
token = jwt.encode(payload, "secret")
> jwt.decode(
token,
"secret",
options={"verify_exp": False},
algorithms=["HS256"],
)
tests/test_api_jwt.py:517:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImV4cCI6MTczMjc2ODMwM30.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': False, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': False, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImV4cCI6MTczMjc2ODMwM30'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_should_raise_error_if_exp_required_but_not_present
test_api_jwt.py::TestJWT::test_decode_should_raise_error_if_exp_required_but_not_present
self =
jwt =
def test_decode_should_raise_error_if_exp_required_but_not_present(self, jwt):
payload = {
"some": "payload",
# exp not present
}
token = jwt.encode(payload, "secret")
with pytest.raises(MissingRequiredClaimError) as exc:
> jwt.decode(
token,
"secret",
options={"require": ["exp"]},
algorithms=["HS256"],
)
tests/test_api_jwt.py:532:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': ['exp'], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': ['exp'], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_should_raise_error_if_iat_required_but_not_present
test_api_jwt.py::TestJWT::test_decode_should_raise_error_if_iat_required_but_not_present
self =
jwt =
def test_decode_should_raise_error_if_iat_required_but_not_present(self, jwt):
payload = {
"some": "payload",
# iat not present
}
token = jwt.encode(payload, "secret")
with pytest.raises(MissingRequiredClaimError) as exc:
> jwt.decode(
token,
"secret",
options={"require": ["iat"]},
algorithms=["HS256"],
)
tests/test_api_jwt.py:549:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': ['iat'], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': ['iat'], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_should_raise_error_if_nbf_required_but_not_present
test_api_jwt.py::TestJWT::test_decode_should_raise_error_if_nbf_required_but_not_present
self =
jwt =
def test_decode_should_raise_error_if_nbf_required_but_not_present(self, jwt):
payload = {
"some": "payload",
# nbf not present
}
token = jwt.encode(payload, "secret")
with pytest.raises(MissingRequiredClaimError) as exc:
> jwt.decode(
token,
"secret",
options={"require": ["nbf"]},
algorithms=["HS256"],
)
tests/test_api_jwt.py:566:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': ['nbf'], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': ['nbf'], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_skip_check_iat
test_api_jwt.py::TestJWT::test_skip_check_iat
self =
jwt =
def test_skip_check_iat(self, jwt):
payload = {
"some": "payload",
"iat": datetime.now(tz=timezone.utc) + timedelta(days=1),
}
token = jwt.encode(payload, "secret")
> jwt.decode(
token,
"secret",
options={"verify_iat": False},
algorithms=["HS256"],
)
tests/test_api_jwt.py:594:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImlhdCI6MTczMjk0MTEwM30.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': False, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': False, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsImlhdCI6MTczMjk0MTEwM30'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_skip_check_nbf
test_api_jwt.py::TestJWT::test_skip_check_nbf
self =
jwt =
def test_skip_check_nbf(self, jwt):
payload = {
"some": "payload",
"nbf": datetime.now(tz=timezone.utc) + timedelta(days=1),
}
token = jwt.encode(payload, "secret")
> jwt.decode(
token,
"secret",
options={"verify_nbf": False},
algorithms=["HS256"],
)
tests/test_api_jwt.py:607:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsIm5iZiI6MTczMjk0MTEwM30.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCIsIm5iZiI6MTczMjk0MTEwM30'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_custom_json_encoder
test_api_jwt.py::TestJWT::test_custom_json_encoder
self =
jwt =
def test_custom_json_encoder(self, jwt):
class CustomJSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Decimal):
return "it worked"
return super().default(o)
data = {"some_decimal": Decimal("2.2")}
with pytest.raises(TypeError):
jwt.encode(data, "secret", algorithms=["HS256"])
token = jwt.encode(data, "secret", json_encoder=CustomJSONEncoder)
> payload = jwt.decode(token, "secret", algorithms=["HS256"])
tests/test_api_jwt.py:627:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lX2RlY2ltYWwiOiJpdCB3b3JrZWQifQ.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzb21lX2RlY2ltYWwiOiJpdCB3b3JrZWQifQ'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_with_verify_exp_option
test_api_jwt.py::TestJWT::test_decode_with_verify_exp_option
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854702, 'iss': 'jeff'}
def test_decode_with_verify_exp_option(self, jwt, payload):
payload["exp"] = utc_timestamp() - 1
secret = "secret"
jwt_message = jwt.encode(payload, secret)
> jwt.decode(
jwt_message,
secret,
algorithms=["HS256"],
options={"verify_exp": False},
)
tests/test_api_jwt.py:636:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzAyLCJjbGFpbSI6Imluc2FuaXR5In0.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': False, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': False, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzAyLCJjbGFpbSI6Imluc2FuaXR5In0'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_with_verify_exp_option_and_signature_off
test_api_jwt.py::TestJWT::test_decode_with_verify_exp_option_and_signature_off
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854702, 'iss': 'jeff'}
def test_decode_with_verify_exp_option_and_signature_off(self, jwt, payload):
payload["exp"] = utc_timestamp() - 1
secret = "secret"
jwt_message = jwt.encode(payload, secret)
> jwt.decode(
jwt_message,
options={"verify_signature": False},
)
tests/test_api_jwt.py:656:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzAyLCJjbGFpbSI6Imluc2FuaXR5In0.'
key = None, algorithms = None, options = {'verify_signature': False}
kwargs = {}
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
decoded = {'header': {'alg': 'none', 'typ': 'JWT'}, 'payload': b'{"iss":"jeff","exp":1732854702,"claim":"insanity"}', 'signature': b''}
payload = {'claim': 'insanity', 'exp': 1732854702, 'iss': 'jeff'}
now = datetime.datetime(2024, 11, 29, 4, 31, 43, 847841, tzinfo=datetime.timezone.utc)
exp = datetime.datetime(2024, 11, 29, 4, 31, 42, tzinfo=datetime.timezone.utc)
leeway = datetime.timedelta(0)
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, **kwargs: Any) -> dict[str, Any]:
"""
Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
merged_options = {**self.options, **(options or {})}
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
payload = self._decode_payload(decoded)
if merged_options['verify_exp'] and 'exp' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
exp = datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
leeway = timedelta(seconds=kwargs.get('leeway', 0))
if now > exp + leeway:
> raise ExpiredSignatureError('Signature has expired')
E jwt.exceptions.ExpiredSignatureError: Signature has expired
jwt/api_jwt.py:119: ExpiredSignatureError
test_api_jwt.py::TestJWT::test_decode_with_optional_algorithms
test_api_jwt.py::TestJWT::test_decode_with_optional_algorithms
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iss': 'jeff'}
def test_decode_with_optional_algorithms(self, jwt, payload):
secret = "secret"
jwt_message = jwt.encode(payload, secret)
with pytest.raises(DecodeError) as exc:
jwt.decode(jwt_message, secret)
> assert (
'It is required that you pass in a value for the "algorithms" argument when calling decode().'
in str(exc.value)
)
E assert 'It is required that you pass in a value for the "algorithms" argument when calling decode().' in 'Algorithm "none" not allowed'
E + where 'Algorithm "none" not allowed' = str(DecodeError('Algorithm "none" not allowed'))
E + where DecodeError('Algorithm "none" not allowed') = .value
tests/test_api_jwt.py:674: AssertionError
test_api_jwt.py::TestJWT::test_decode_no_algorithms_verify_signature_false
test_api_jwt.py::TestJWT::test_decode_no_algorithms_verify_signature_false
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iss': 'jeff'}
def test_decode_no_algorithms_verify_signature_false(self, jwt, payload):
secret = "secret"
jwt_message = jwt.encode(payload, secret)
> jwt.decode(jwt_message, secret, options={"verify_signature": False})
tests/test_api_jwt.py:683:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5In0.'
key = 'secret'
algorithms = ['ES384', 'RS384', 'EdDSA', 'HS256', 'RS256', 'RS512', ...]
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5In0'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
raise InvalidAlgorithmError('The specified alg value is not allowed')
if alg == 'none':
if merged_options['verify_signature']:
raise DecodeError('Algorithm "none" not allowed')
if key not in [None, '', 'none']:
> raise InvalidKeyError('When alg = "none", key must be empty or "none"')
E NameError: name 'InvalidKeyError' is not defined
jwt/api_jws.py:284: NameError
test_api_jwt.py::TestJWT::test_decode_legacy_verify_warning
test_api_jwt.py::TestJWT::test_decode_legacy_verify_warning
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iss': 'jeff'}
def test_decode_legacy_verify_warning(self, jwt, payload):
secret = "secret"
jwt_message = jwt.encode(payload, secret)
with pytest.deprecated_call():
# The implicit default for options.verify_signature is True,
# but the user sets verify to False.
> jwt.decode(jwt_message, secret, verify=False, algorithms=["HS256"])
tests/test_api_jwt.py:692:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5In0.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5In0'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
During handling of the above exception, another exception occurred:
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iss': 'jeff'}
def test_decode_legacy_verify_warning(self, jwt, payload):
secret = "secret"
jwt_message = jwt.encode(payload, secret)
> with pytest.deprecated_call():
E Failed: DID NOT WARN. No warnings of type (, , ) were emitted.
E Emitted warnings: [].
tests/test_api_jwt.py:689: Failed
test_api_jwt.py::TestJWT::test_decode_no_options_mutation
test_api_jwt.py::TestJWT::test_decode_no_options_mutation
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iss': 'jeff'}
def test_decode_no_options_mutation(self, jwt, payload):
options = {"verify_signature": True}
orig_options = options.copy()
secret = "secret"
jwt_message = jwt.encode(payload, secret)
> jwt.decode(jwt_message, secret, options=options, algorithms=["HS256"])
tests/test_api_jwt.py:706:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5In0.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5In0'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_warns_on_unsupported_kwarg
test_api_jwt.py::TestJWT::test_decode_warns_on_unsupported_kwarg
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iss': 'jeff'}
def test_decode_warns_on_unsupported_kwarg(self, jwt, payload):
secret = "secret"
jwt_message = jwt.encode(payload, secret)
with pytest.warns(RemovedInPyjwt3Warning) as record:
> jwt.decode(jwt_message, secret, algorithms=["HS256"], foo="bar")
tests/test_api_jwt.py:714:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5In0.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE4LCJjbGFpbSI6Imluc2FuaXR5In0'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
During handling of the above exception, another exception occurred:
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854718, 'iss': 'jeff'}
def test_decode_warns_on_unsupported_kwarg(self, jwt, payload):
secret = "secret"
jwt_message = jwt.encode(payload, secret)
> with pytest.warns(RemovedInPyjwt3Warning) as record:
E Failed: DID NOT WARN. No warnings of type (,) were emitted.
E Emitted warnings: [].
tests/test_api_jwt.py:713: Failed
test_api_jwt.py::TestJWT::test_decode_complete_warns_on_unsupported_kwarg
test_api_jwt.py::TestJWT::test_decode_complete_warns_on_unsupported_kwarg
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854719, 'iss': 'jeff'}
def test_decode_complete_warns_on_unsupported_kwarg(self, jwt, payload):
secret = "secret"
jwt_message = jwt.encode(payload, secret)
with pytest.warns(RemovedInPyjwt3Warning) as record:
> jwt.decode_complete(jwt_message, secret, algorithms=["HS256"], foo="bar")
tests/test_api_jwt.py:723:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE5LCJjbGFpbSI6Imluc2FuaXR5In0.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE5LCJjbGFpbSI6Imluc2FuaXR5In0'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
During handling of the above exception, another exception occurred:
self =
jwt =
payload = {'claim': 'insanity', 'exp': 1732854719, 'iss': 'jeff'}
def test_decode_complete_warns_on_unsupported_kwarg(self, jwt, payload):
secret = "secret"
jwt_message = jwt.encode(payload, secret)
> with pytest.warns(RemovedInPyjwt3Warning) as record:
E Failed: DID NOT WARN. No warnings of type (,) were emitted.
E Emitted warnings: [].
tests/test_api_jwt.py:722: Failed
test_api_jwt.py::TestJWT::test_decode_strict_aud_forbids_list_audience
test_api_jwt.py::TestJWT::test_decode_strict_aud_forbids_list_audience
self =
jwt =
payload = {'aud': 'urn:foo', 'claim': 'insanity', 'exp': 1732854719, 'iss': 'jeff'}
def test_decode_strict_aud_forbids_list_audience(self, jwt, payload):
secret = "secret"
payload["aud"] = "urn:foo"
jwt_message = jwt.encode(payload, secret)
# Decodes without `strict_aud`.
> jwt.decode(
jwt_message,
secret,
audience=["urn:foo", "urn:bar"],
options={"strict_aud": False},
algorithms=["HS256"],
)
tests/test_api_jwt.py:733:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE5LCJjbGFpbSI6Imluc2FuaXR5IiwiYXVkIjoidXJuOmZvbyJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'strict_aud': False, 'verify_aud': True, 'verify_exp': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'strict_aud': False, 'verify_aud': True, 'verify_exp': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE5LCJjbGFpbSI6Imluc2FuaXR5IiwiYXVkIjoidXJuOmZvbyJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_strict_aud_forbids_list_claim
test_api_jwt.py::TestJWT::test_decode_strict_aud_forbids_list_claim
self =
jwt =
payload = {'aud': ['urn:foo', 'urn:bar'], 'claim': 'insanity', 'exp': 1732854719, 'iss': 'jeff'}
def test_decode_strict_aud_forbids_list_claim(self, jwt, payload):
secret = "secret"
payload["aud"] = ["urn:foo", "urn:bar"]
jwt_message = jwt.encode(payload, secret)
# Decodes without `strict_aud`.
> jwt.decode(
jwt_message,
secret,
audience="urn:foo",
options={"strict_aud": False},
algorithms=["HS256"],
)
tests/test_api_jwt.py:757:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE5LCJjbGFpbSI6Imluc2FuaXR5IiwiYXVkIjpbInVybjpmb28iLCJ1cm46YmFyIl19.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'strict_aud': False, 'verify_aud': True, 'verify_exp': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'strict_aud': False, 'verify_aud': True, 'verify_exp': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE5LCJjbGFpbSI6Imluc2FuaXR5IiwiYXVkIjpbInVybjpmb28iLCJ1cm46YmFyIl19'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_strict_aud_does_not_match
test_api_jwt.py::TestJWT::test_decode_strict_aud_does_not_match
self =
jwt =
payload = {'aud': 'urn:foo', 'claim': 'insanity', 'exp': 1732854719, 'iss': 'jeff'}
def test_decode_strict_aud_does_not_match(self, jwt, payload):
secret = "secret"
payload["aud"] = "urn:foo"
jwt_message = jwt.encode(payload, secret)
with pytest.raises(
InvalidAudienceError, match=r"Audience doesn't match \(strict\)"
):
> jwt.decode(
jwt_message,
secret,
audience="urn:bar",
options={"strict_aud": True},
algorithms=["HS256"],
)
tests/test_api_jwt.py:785:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE5LCJjbGFpbSI6Imluc2FuaXR5IiwiYXVkIjoidXJuOmZvbyJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'strict_aud': True, 'verify_aud': True, 'verify_exp': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'strict_aud': True, 'verify_aud': True, 'verify_exp': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE5LCJjbGFpbSI6Imluc2FuaXR5IiwiYXVkIjoidXJuOmZvbyJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_api_jwt.py::TestJWT::test_decode_strict_ok
test_api_jwt.py::TestJWT::test_decode_strict_ok
self =
jwt =
payload = {'aud': 'urn:foo', 'claim': 'insanity', 'exp': 1732854719, 'iss': 'jeff'}
def test_decode_strict_ok(self, jwt, payload):
secret = "secret"
payload["aud"] = "urn:foo"
jwt_message = jwt.encode(payload, secret)
> jwt.decode(
jwt_message,
secret,
audience="urn:foo",
options={"strict_aud": True},
algorithms=["HS256"],
)
tests/test_api_jwt.py:798:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/api_jwt.py:170: in decode
decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
jwt/api_jwt.py:111: in decode_complete
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE5LCJjbGFpbSI6Imluc2FuaXR5IiwiYXVkIjoidXJuOmZvbyJ9.'
key = 'secret', algorithms = ['HS256']
options = {'require': [], 'strict_aud': True, 'verify_aud': True, 'verify_exp': True, ...}
detached_payload = None, kwargs = {}
deprecated_kwargs = {'verify': 'verify_signature', 'verify_aud': 'verify_aud', 'verify_exp': 'verify_exp', 'verify_iat': 'verify_iat', ...}
old_name = 'verify_iss', new_name = 'verify_iss'
merged_options = {'require': [], 'strict_aud': True, 'verify_aud': True, 'verify_exp': True, ...}
signing_input = b'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJqZWZmIiwiZXhwIjoxNzMyODU0NzE5LCJjbGFpbSI6Imluc2FuaXR5IiwiYXVkIjoidXJuOmZvbyJ9'
crypto_segment = b''
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
"""Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
detached_payload: The detached payload to use for verification.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
deprecated_kwargs = {
'verify': 'verify_signature',
'verify_exp': 'verify_exp',
'verify_iat': 'verify_iat',
'verify_nbf': 'verify_nbf',
'verify_aud': 'verify_aud',
'verify_iss': 'verify_iss',
}
options = options or {}
for old_name, new_name in deprecated_kwargs.items():
if old_name in kwargs:
warnings.warn(
f'The {old_name} parameter is deprecated. '
f'Please use {new_name} in options instead.',
category=DeprecationWarning,
stacklevel=2
)
options[new_name] = kwargs.pop(old_name)
for kwarg in kwargs:
warnings.warn(
f'The "{kwarg}" argument is not supported and will be ignored.',
category=RemovedInPyjwt3Warning,
stacklevel=2
)
merged_options = {**self.options}
if options:
if not isinstance(options, dict):
raise TypeError('options must be a dict')
merged_options.update(options)
if isinstance(jwt, str):
jwt = jwt.encode('utf-8')
if not isinstance(jwt, bytes):
raise DecodeError('Invalid token type')
try:
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
header_segment, payload_segment = signing_input.split(b'.', 1)
except ValueError:
raise InvalidTokenError('Not enough segments')
try:
header_data = base64url_decode(header_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid header padding')
try:
header = json.loads(header_data.decode('utf-8'))
except ValueError as e:
raise DecodeError('Invalid header string: %s' % e)
if not isinstance(header, dict):
raise DecodeError('Invalid header string: must be a json object')
if header.get('b64', True):
try:
payload = base64url_decode(payload_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid payload padding')
else:
if detached_payload is None:
raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
payload = detached_payload
try:
signature = base64url_decode(crypto_segment)
except (TypeError, binascii.Error):
raise DecodeError('Invalid crypto padding')
if algorithms is None:
algorithms = list(self._valid_algs)
if not algorithms and merged_options['verify_signature']:
raise DecodeError('No algorithms were specified')
try:
alg = header['alg']
except KeyError:
raise InvalidTokenError('Missing algorithm ("alg") in headers')
if alg not in algorithms:
> raise InvalidAlgorithmError('The specified alg value is not allowed')
E jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
jwt/api_jws.py:278: InvalidAlgorithmError
test_jwks_client.py::TestPyJWKClient::test_get_signing_keys_raises_if_none_found
test_jwks_client.py::TestPyJWKClient::test_get_signing_keys_raises_if_none_found
self =
def test_get_signing_keys_raises_if_none_found(self):
url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
mocked_key = RESPONSE_DATA_WITH_MATCHING_KID["keys"][0].copy()
mocked_key["use"] = "enc"
response = {"keys": [mocked_key]}
with mocked_success_response(response):
jwks_client = PyJWKClient(url)
with pytest.raises(PyJWKClientError) as exc:
jwks_client.get_signing_keys()
> assert "The JWKS endpoint did not contain any signing keys" in str(exc.value)
E AssertionError: assert 'The JWKS endpoint did not contain any signing keys' in 'No signing keys found in JWKS'
E + where 'No signing keys found in JWKS' = str(PyJWKClientError('No signing keys found in JWKS'))
E + where PyJWKClientError('No signing keys found in JWKS') = .value
tests/test_jwks_client.py:149: AssertionError
test_jwks_client.py::TestPyJWKClient::test_get_signing_key_from_jwt
test_jwks_client.py::TestPyJWKClient::test_get_signing_key_from_jwt
self =
token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRd...kc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA'
refresh_jwks = True
def get_signing_key_from_jwt(self, token: str, refresh_jwks: bool=True) -> PyJWK:
"""Return the signing key from the JWKS that matches the kid in the token header.
Args:
token: The JWT token to get the key for.
refresh_jwks: Whether to refresh the JWKS if the key is not found.
"""
try:
> headers = decode_token(token, options={'verify_signature': False})['header']
jwt/jwks_client.py:109:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRd...kc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA'
key = None, algorithms = None, options = {'verify_signature': False}
kwargs = {}
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
decoded = {'header': {'alg': 'RS256', 'kid': 'NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw', 'typ': 'JWT'}, 'payload':...f;\x1b\xdf]5K\\\xbd\xa3zNfx~\xa7\xf9U\xa0\xac\x17h0Q\xfd\xdd\x01,\xcc@\xe8\xc00\x04\x85\x03\xc7V\xcd1\x8f\xb4\x88\xcc'}
payload = {'aud': 'https://expenses-api', 'azp': 'aW4Cca79xReLWUz0aE2H6kD0O3cXBVtC', 'exp': 1572006964, 'gty': 'client-credentials', ...}
now = datetime.datetime(2024, 11, 29, 4, 31, 44, 182936, tzinfo=datetime.timezone.utc)
exp = datetime.datetime(2019, 10, 25, 12, 36, 4, tzinfo=datetime.timezone.utc)
leeway = datetime.timedelta(0)
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, **kwargs: Any) -> dict[str, Any]:
"""
Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
merged_options = {**self.options, **(options or {})}
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
payload = self._decode_payload(decoded)
if merged_options['verify_exp'] and 'exp' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
exp = datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
leeway = timedelta(seconds=kwargs.get('leeway', 0))
if now > exp + leeway:
> raise ExpiredSignatureError('Signature has expired')
E jwt.exceptions.ExpiredSignatureError: Signature has expired
jwt/api_jwt.py:119: ExpiredSignatureError
During handling of the above exception, another exception occurred:
self =
def test_get_signing_key_from_jwt(self):
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA"
url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
with mocked_success_response(RESPONSE_DATA_WITH_MATCHING_KID):
jwks_client = PyJWKClient(url)
> signing_key = jwks_client.get_signing_key_from_jwt(token)
tests/test_jwks_client.py:202:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRd...kc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA'
refresh_jwks = True
def get_signing_key_from_jwt(self, token: str, refresh_jwks: bool=True) -> PyJWK:
"""Return the signing key from the JWKS that matches the kid in the token header.
Args:
token: The JWT token to get the key for.
refresh_jwks: Whether to refresh the JWKS if the key is not found.
"""
try:
headers = decode_token(token, options={'verify_signature': False})['header']
except Exception as e:
> raise PyJWKClientError(f'Failed to decode JWT headers: {str(e)}')
E jwt.exceptions.PyJWKClientError: Failed to decode JWT headers: Signature has expired
jwt/jwks_client.py:111: PyJWKClientError
test_jwks_client.py::TestPyJWKClient::test_failed_request_should_raise_connection_error
test_jwks_client.py::TestPyJWKClient::test_failed_request_should_raise_connection_error
self =
token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRd...kc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA'
refresh_jwks = True
def get_signing_key_from_jwt(self, token: str, refresh_jwks: bool=True) -> PyJWK:
"""Return the signing key from the JWKS that matches the kid in the token header.
Args:
token: The JWT token to get the key for.
refresh_jwks: Whether to refresh the JWKS if the key is not found.
"""
try:
> headers = decode_token(token, options={'verify_signature': False})['header']
jwt/jwks_client.py:109:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRd...kc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA'
key = None, algorithms = None, options = {'verify_signature': False}
kwargs = {}
merged_options = {'require': [], 'verify_aud': True, 'verify_exp': True, 'verify_iat': True, ...}
decoded = {'header': {'alg': 'RS256', 'kid': 'NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw', 'typ': 'JWT'}, 'payload':...f;\x1b\xdf]5K\\\xbd\xa3zNfx~\xa7\xf9U\xa0\xac\x17h0Q\xfd\xdd\x01,\xcc@\xe8\xc00\x04\x85\x03\xc7V\xcd1\x8f\xb4\x88\xcc'}
payload = {'aud': 'https://expenses-api', 'azp': 'aW4Cca79xReLWUz0aE2H6kD0O3cXBVtC', 'exp': 1572006964, 'gty': 'client-credentials', ...}
now = datetime.datetime(2024, 11, 29, 4, 31, 48, 215542, tzinfo=datetime.timezone.utc)
exp = datetime.datetime(2019, 10, 25, 12, 36, 4, tzinfo=datetime.timezone.utc)
leeway = datetime.timedelta(0)
def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, **kwargs: Any) -> dict[str, Any]:
"""
Decodes a JWT and returns a dict of the token contents.
Args:
jwt: The JWT to decode.
key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
options: A dict of options for decoding. If None, use default options.
**kwargs: Additional options for decoding.
Returns:
A dict including:
- header: A dict of the JWT header
- payload: The decoded payload
- signature: The signature of the JWT
"""
merged_options = {**self.options, **(options or {})}
decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
payload = self._decode_payload(decoded)
if merged_options['verify_exp'] and 'exp' in payload:
now = kwargs.get('now', datetime.now(timezone.utc))
exp = datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
leeway = timedelta(seconds=kwargs.get('leeway', 0))
if now > exp + leeway:
> raise ExpiredSignatureError('Signature has expired')
E jwt.exceptions.ExpiredSignatureError: Signature has expired
jwt/api_jwt.py:119: ExpiredSignatureError
During handling of the above exception, another exception occurred:
self =
def test_failed_request_should_raise_connection_error(self):
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA"
url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
jwks_client = PyJWKClient(url)
with pytest.raises(PyJWKClientConnectionError):
with mocked_failed_response():
> jwks_client.get_signing_key_from_jwt(token)
tests/test_jwks_client.py:294:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRd...kc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA'
refresh_jwks = True
def get_signing_key_from_jwt(self, token: str, refresh_jwks: bool=True) -> PyJWK:
"""Return the signing key from the JWKS that matches the kid in the token header.
Args:
token: The JWT token to get the key for.
refresh_jwks: Whether to refresh the JWKS if the key is not found.
"""
try:
headers = decode_token(token, options={'verify_signature': False})['header']
except Exception as e:
> raise PyJWKClientError(f'Failed to decode JWT headers: {str(e)}')
E jwt.exceptions.PyJWKClientError: Failed to decode JWT headers: Signature has expired
jwt/jwks_client.py:111: PyJWKClientError
test_jwks_client.py::TestPyJWKClient::test_get_jwt_set_refresh_cache
test_jwks_client.py::TestPyJWKClient::test_get_jwt_set_refresh_cache
self =
def test_get_jwt_set_refresh_cache(self):
url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
jwks_client = PyJWKClient(url)
kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw"
# The first call will return response with no matching kid,
# the function should make another call to try to refresh the cache.
with mocked_first_call_wrong_kid_second_call_correct_kid(
RESPONSE_DATA_NO_MATCHING_KID, RESPONSE_DATA_WITH_MATCHING_KID
) as call_data:
> jwks_client.get_signing_key(kid)
tests/test_jwks_client.py:307:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
kid = 'NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw'
def get_signing_key(self, kid: str) -> PyJWK:
"""Return the signing key from the JWKS that matches the provided kid.
Args:
kid: The key ID to search for.
"""
signing_keys = self.get_signing_keys()
for key in signing_keys:
if key.key_id == kid:
return key
# If no key is found, try refreshing the JWKS once
signing_keys = self.get_signing_keys()
for key in signing_keys:
if key.key_id == kid:
return key
> raise PyJWKClientError(f'Unable to find a signing key that matches: {kid}')
E jwt.exceptions.PyJWKClientError: Unable to find a signing key that matches: NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw
jwt/jwks_client.py:99: PyJWKClientError
test_jwks_client.py::TestPyJWKClient::test_get_jwt_set_timeout
test_jwks_client.py::TestPyJWKClient::test_get_jwt_set_timeout
self =
def test_get_jwt_set_timeout(self):
url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"
jwks_client = PyJWKClient(url, timeout=5)
with pytest.raises(PyJWKClientError) as exc:
with mocked_timeout():
> jwks_client.get_jwk_set()
tests/test_jwks_client.py:336:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
jwt/jwks_client.py:55: in get_jwk_set
data = self.fetch_data()
jwt/jwks_client.py:38: in fetch_data
response = urllib.request.urlopen(request, timeout=self.timeout)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/unittest/mock.py:1137: in __call__
return self._mock_call(*args, **kwargs)
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/unittest/mock.py:1141: in _mock_call
return self._execute_mock_call(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self =
args = (,)
kwargs = {'timeout': 5}, effect = TimeoutError('timed out')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E TimeoutError: timed out
/root/.local/share/uv/python/cpython-3.12.6-linux-x86_64-gnu/lib/python3.12/unittest/mock.py:1196: TimeoutError
test_utils.py::test_to_base64url_uint[-1-]
test_utils.py::test_to_base64url_uint[-1-]
inputval = -1, expected = ''
@pytest.mark.parametrize(
"inputval,expected",
[
(0, b"AA"),
(1, b"AQ"),
(255, b"_w"),
(65537, b"AQAB"),
(123456789, b"B1vNFQ"),
pytest.param(-1, "", marks=pytest.mark.xfail(raises=ValueError)),
],
)
def test_to_base64url_uint(inputval, expected):
> actual = to_base64url_uint(inputval)
tests/test_utils.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
val = -1
def to_base64url_uint(val: int) -> bytes:
if val < 0:
> raise ValueError('Must be a positive integer')
E ValueError: Must be a positive integer
jwt/utils.py:34: ValueError
Patch diff
diff --git a/jwt/algorithms.py b/jwt/algorithms.py
index b3897bf..0a18f4e 100644
--- a/jwt/algorithms.py
+++ b/jwt/algorithms.py
@@ -38,7 +38,28 @@ def get_default_algorithms() -> dict[str, Algorithm]:
"""
Returns the algorithms that are implemented by the library.
"""
- pass
+ default_algorithms = {
+ 'none': NoneAlgorithm(),
+ 'HS256': HMACAlgorithm(HMACAlgorithm.SHA256),
+ 'HS384': HMACAlgorithm(HMACAlgorithm.SHA384),
+ 'HS512': HMACAlgorithm(HMACAlgorithm.SHA512),
+ }
+
+ if has_crypto:
+ default_algorithms.update({
+ 'RS256': RSAAlgorithm(RSAAlgorithm.SHA256),
+ 'RS384': RSAAlgorithm(RSAAlgorithm.SHA384),
+ 'RS512': RSAAlgorithm(RSAAlgorithm.SHA512),
+ 'ES256': ECAlgorithm(ECAlgorithm.SHA256),
+ 'ES384': ECAlgorithm(ECAlgorithm.SHA384),
+ 'ES512': ECAlgorithm(ECAlgorithm.SHA512),
+ 'PS256': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256),
+ 'PS384': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384),
+ 'PS512': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512),
+ 'EdDSA': OKPAlgorithm(),
+ })
+
+ return default_algorithms
class Algorithm(ABC):
"""
@@ -51,7 +72,17 @@ class Algorithm(ABC):
If there is no hash algorithm, raises a NotImplementedError.
"""
- pass
+ if not hasattr(self, 'hash_alg'):
+ raise NotImplementedError('Algorithm does not have a hash algorithm')
+
+ if has_crypto and isinstance(self.hash_alg, type) and issubclass(self.hash_alg, hashes.HashAlgorithm):
+ h = hashes.Hash(self.hash_alg(), backend=default_backend())
+ h.update(bytestr)
+ return h.finalize()
+ else:
+ h = self.hash_alg()
+ h.update(bytestr)
+ return h.digest()
@abstractmethod
def prepare_key(self, key: Any) -> Any:
@@ -98,6 +129,24 @@ class NoneAlgorithm(Algorithm):
Placeholder for use when no signing or verification
operations are required.
"""
+ def prepare_key(self, key: Any) -> None:
+ if key not in [None, '', 'none']:
+ raise InvalidKeyError('When alg = "none", key must be empty or "none"')
+ return None
+
+ def sign(self, msg: bytes, key: Any) -> bytes:
+ return b''
+
+ def verify(self, msg: bytes, key: Any, sig: bytes) -> bool:
+ return sig == b''
+
+ @staticmethod
+ def to_jwk(key_obj: Any, as_dict: bool = False) -> NoReturn:
+ raise NotImplementedError('Algorithm "none" can\'t be exported as JWK')
+
+ @staticmethod
+ def from_jwk(jwk: str | JWKDict) -> NoReturn:
+ raise NotImplementedError('Algorithm "none" can\'t be imported from JWK')
class HMACAlgorithm(Algorithm):
"""
@@ -110,6 +159,51 @@ class HMACAlgorithm(Algorithm):
def __init__(self, hash_alg: HashlibHash) -> None:
self.hash_alg = hash_alg
+
+ def prepare_key(self, key: Union[str, bytes]) -> bytes:
+ if not isinstance(key, (str, bytes)):
+ raise TypeError('Expected a string value')
+ key = force_bytes(key)
+ if is_pem_format(key) or is_ssh_key(key):
+ raise InvalidKeyError('The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.')
+ return key
+
+ def sign(self, msg: bytes, key: Union[str, bytes]) -> bytes:
+ key = self.prepare_key(key)
+ h = hmac.new(key, msg, self.hash_alg)
+ return h.digest()
+
+ def verify(self, msg: bytes, key: Union[str, bytes], sig: bytes) -> bool:
+ key = self.prepare_key(key)
+ h = hmac.new(key, msg, self.hash_alg)
+ try:
+ return hmac.compare_digest(sig, h.digest())
+ except TypeError:
+ return False
+
+ @staticmethod
+ def to_jwk(key_obj: Union[str, bytes], as_dict: bool = False) -> Union[str, JWKDict]:
+ key_bytes = force_bytes(key_obj)
+ jwk = {
+ 'kty': 'oct',
+ 'k': base64url_encode(key_bytes).decode('ascii')
+ }
+ if as_dict:
+ return jwk
+ return json.dumps(jwk)
+
+ @staticmethod
+ def from_jwk(jwk: Union[str, JWKDict]) -> bytes:
+ if isinstance(jwk, str):
+ jwk = json.loads(jwk)
+ if not isinstance(jwk, dict):
+ raise InvalidKeyError('Key must be a dict or a string')
+ if jwk.get('kty') != 'oct':
+ raise InvalidKeyError('Not an HMAC key')
+ k = jwk.get('k')
+ if not k:
+ raise InvalidKeyError('k parameter is required')
+ return base64url_decode(k)
if has_crypto:
class RSAAlgorithm(Algorithm):
@@ -124,6 +218,107 @@ if has_crypto:
def __init__(self, hash_alg: type[hashes.HashAlgorithm]) -> None:
self.hash_alg = hash_alg
+ def prepare_key(self, key: Union[str, bytes, RSAPrivateKey, RSAPublicKey]) -> Union[RSAPrivateKey, RSAPublicKey]:
+ if isinstance(key, (RSAPrivateKey, RSAPublicKey)):
+ return key
+
+ key = force_bytes(key)
+ if is_pem_format(key):
+ try:
+ return load_pem_private_key(key, password=None, backend=default_backend())
+ except ValueError:
+ try:
+ return load_pem_public_key(key, backend=default_backend())
+ except ValueError:
+ raise InvalidKeyError('Invalid PEM format')
+ elif is_ssh_key(key):
+ try:
+ return load_ssh_public_key(key, backend=default_backend())
+ except ValueError:
+ raise InvalidKeyError('Invalid SSH key format')
+ else:
+ raise InvalidKeyError('Invalid key format')
+
+ def sign(self, msg: bytes, key: Union[str, bytes, RSAPrivateKey]) -> bytes:
+ key_obj = self.prepare_key(key)
+ if not isinstance(key_obj, RSAPrivateKey):
+ raise TypeError('Key must be an RSAPrivateKey instance')
+
+ padder = padding.PKCS1v15()
+ return key_obj.sign(msg, padder, self.hash_alg())
+
+ def verify(self, msg: bytes, key: Union[str, bytes, RSAPrivateKey, RSAPublicKey], sig: bytes) -> bool:
+ key_obj = self.prepare_key(key)
+ if not isinstance(key_obj, (RSAPrivateKey, RSAPublicKey)):
+ raise TypeError('Key must be an RSA key instance')
+
+ verifier = key_obj if isinstance(key_obj, RSAPublicKey) else key_obj.public_key()
+ padder = padding.PKCS1v15()
+ try:
+ verifier.verify(sig, msg, padder, self.hash_alg())
+ return True
+ except InvalidSignature:
+ return False
+
+ @staticmethod
+ def to_jwk(key_obj: Union[RSAPrivateKey, RSAPublicKey], as_dict: bool = False) -> Union[str, JWKDict]:
+ if isinstance(key_obj, RSAPrivateKey):
+ numbers = key_obj.private_numbers()
+ jwk = {
+ 'kty': 'RSA',
+ 'n': to_base64url_uint(numbers.public_numbers.n).decode('ascii'),
+ 'e': to_base64url_uint(numbers.public_numbers.e).decode('ascii'),
+ 'd': to_base64url_uint(numbers.d).decode('ascii'),
+ 'p': to_base64url_uint(numbers.p).decode('ascii'),
+ 'q': to_base64url_uint(numbers.q).decode('ascii'),
+ 'dp': to_base64url_uint(numbers.dmp1).decode('ascii'),
+ 'dq': to_base64url_uint(numbers.dmq1).decode('ascii'),
+ 'qi': to_base64url_uint(numbers.iqmp).decode('ascii')
+ }
+ else:
+ numbers = key_obj.public_numbers()
+ jwk = {
+ 'kty': 'RSA',
+ 'n': to_base64url_uint(numbers.n).decode('ascii'),
+ 'e': to_base64url_uint(numbers.e).decode('ascii')
+ }
+
+ if as_dict:
+ return jwk
+ return json.dumps(jwk)
+
+ @staticmethod
+ def from_jwk(jwk: Union[str, JWKDict]) -> Union[RSAPrivateKey, RSAPublicKey]:
+ if isinstance(jwk, str):
+ jwk = json.loads(jwk)
+ if not isinstance(jwk, dict):
+ raise InvalidKeyError('Key must be a dict or a string')
+ if jwk.get('kty') != 'RSA':
+ raise InvalidKeyError('Not an RSA key')
+
+ if 'd' in jwk and 'p' in jwk and 'q' in jwk:
+ # Private key
+ numbers = RSAPrivateNumbers(
+ d=from_base64url_uint(jwk['d']),
+ p=from_base64url_uint(jwk['p']),
+ q=from_base64url_uint(jwk['q']),
+ dmp1=from_base64url_uint(jwk['dp']),
+ dmq1=from_base64url_uint(jwk['dq']),
+ iqmp=from_base64url_uint(jwk['qi']),
+ public_numbers=RSAPublicNumbers(
+ e=from_base64url_uint(jwk['e']),
+ n=from_base64url_uint(jwk['n'])
+ )
+ )
+ return numbers.private_key(backend=default_backend())
+ else:
+ # Public key
+ numbers = RSAPublicNumbers(
+ e=from_base64url_uint(jwk['e']),
+ n=from_base64url_uint(jwk['n'])
+ )
+ return numbers.public_key(backend=default_backend())
+
class ECAlgorithm(Algorithm):
"""
Performs signing and verification operations using
@@ -136,10 +331,150 @@ if has_crypto:
def __init__(self, hash_alg: type[hashes.HashAlgorithm]) -> None:
self.hash_alg = hash_alg
+ def prepare_key(self, key: Union[str, bytes, EllipticCurvePrivateKey, EllipticCurvePublicKey]) -> Union[EllipticCurvePrivateKey, EllipticCurvePublicKey]:
+ if isinstance(key, (EllipticCurvePrivateKey, EllipticCurvePublicKey)):
+ return key
+
+ key = force_bytes(key)
+ if is_pem_format(key):
+ try:
+ return load_pem_private_key(key, password=None, backend=default_backend())
+ except ValueError:
+ try:
+ return load_pem_public_key(key, backend=default_backend())
+ except ValueError:
+ raise InvalidKeyError('Invalid PEM format')
+ elif is_ssh_key(key):
+ try:
+ return load_ssh_public_key(key, backend=default_backend())
+ except ValueError:
+ raise InvalidKeyError('Invalid SSH key format')
+ else:
+ raise InvalidKeyError('Invalid key format')
+
+ def sign(self, msg: bytes, key: Union[str, bytes, EllipticCurvePrivateKey]) -> bytes:
+ key_obj = self.prepare_key(key)
+ if not isinstance(key_obj, EllipticCurvePrivateKey):
+ raise TypeError('Key must be an EllipticCurvePrivateKey instance')
+
+ signature = key_obj.sign(msg, ECDSA(self.hash_alg()))
+ return der_to_raw_signature(signature, key_obj.curve)
+
+ def verify(self, msg: bytes, key: Union[str, bytes, EllipticCurvePrivateKey, EllipticCurvePublicKey], sig: bytes) -> bool:
+ key_obj = self.prepare_key(key)
+ if not isinstance(key_obj, (EllipticCurvePrivateKey, EllipticCurvePublicKey)):
+ raise TypeError('Key must be an EC key instance')
+
+ verifier = key_obj if isinstance(key_obj, EllipticCurvePublicKey) else key_obj.public_key()
+ curve = verifier.curve
+
+ try:
+ der_sig = raw_to_der_signature(sig, curve)
+ verifier.verify(der_sig, msg, ECDSA(self.hash_alg()))
+ return True
+ except (InvalidSignature, ValueError):
+ return False
+
+ @staticmethod
+ def to_jwk(key_obj: Union[EllipticCurvePrivateKey, EllipticCurvePublicKey], as_dict: bool = False) -> Union[str, JWKDict]:
+ if isinstance(key_obj, EllipticCurvePrivateKey):
+ numbers = key_obj.private_numbers()
+ jwk = {
+ 'kty': 'EC',
+ 'crv': {
+ SECP256K1: 'P-256K',
+ SECP256R1: 'P-256',
+ SECP384R1: 'P-384',
+ SECP521R1: 'P-521'
+ }[type(numbers.public_numbers.curve)],
+ 'x': to_base64url_uint(numbers.public_numbers.x).decode('ascii'),
+ 'y': to_base64url_uint(numbers.public_numbers.y).decode('ascii'),
+ 'd': to_base64url_uint(numbers.private_value).decode('ascii')
+ }
+ else:
+ numbers = key_obj.public_numbers()
+ jwk = {
+ 'kty': 'EC',
+ 'crv': {
+ SECP256K1: 'P-256K',
+ SECP256R1: 'P-256',
+ SECP384R1: 'P-384',
+ SECP521R1: 'P-521'
+ }[type(numbers.curve)],
+ 'x': to_base64url_uint(numbers.x).decode('ascii'),
+ 'y': to_base64url_uint(numbers.y).decode('ascii')
+ }
+
+ if as_dict:
+ return jwk
+ return json.dumps(jwk)
+
+ @staticmethod
+ def from_jwk(jwk: Union[str, JWKDict]) -> Union[EllipticCurvePrivateKey, EllipticCurvePublicKey]:
+ if isinstance(jwk, str):
+ jwk = json.loads(jwk)
+ if not isinstance(jwk, dict):
+ raise InvalidKeyError('Key must be a dict or a string')
+ if jwk.get('kty') != 'EC':
+ raise InvalidKeyError('Not an EC key')
+
+ curve = {
+ 'P-256K': SECP256K1,
+ 'P-256': SECP256R1,
+ 'P-384': SECP384R1,
+ 'P-521': SECP521R1
+ }[jwk['crv']]()
+
+ if 'd' in jwk:
+ # Private key
+ numbers = EllipticCurvePrivateNumbers(
+ private_value=from_base64url_uint(jwk['d']),
+ public_numbers=EllipticCurvePublicNumbers(
+ x=from_base64url_uint(jwk['x']),
+ y=from_base64url_uint(jwk['y']),
+ curve=curve
+ )
+ )
+ return numbers.private_key(backend=default_backend())
+ else:
+ # Public key
+ numbers = EllipticCurvePublicNumbers(
+ x=from_base64url_uint(jwk['x']),
+ y=from_base64url_uint(jwk['y']),
+ curve=curve
+ )
+ return numbers.public_key(backend=default_backend())
+
class RSAPSSAlgorithm(RSAAlgorithm):
"""
Performs a signature using RSASSA-PSS with MGF1
"""
+ def sign(self, msg: bytes, key: Union[str, bytes, RSAPrivateKey]) -> bytes:
+ key_obj = self.prepare_key(key)
+ if not isinstance(key_obj, RSAPrivateKey):
+ raise TypeError('Key must be an RSAPrivateKey instance')
+
+ padder = padding.PSS(
+ mgf=padding.MGF1(self.hash_alg()),
+ salt_length=padding.PSS.MAX_LENGTH
+ )
+ return key_obj.sign(msg, padder, self.hash_alg())
+
+ def verify(self, msg: bytes, key: Union[str, bytes, RSAPrivateKey, RSAPublicKey], sig: bytes) -> bool:
+ key_obj = self.prepare_key(key)
+ if not isinstance(key_obj, (RSAPrivateKey, RSAPublicKey)):
+ raise TypeError('Key must be an RSA key instance')
+
+ verifier = key_obj if isinstance(key_obj, RSAPublicKey) else key_obj.public_key()
+ padder = padding.PSS(
+ mgf=padding.MGF1(self.hash_alg()),
+ salt_length=padding.PSS.MAX_LENGTH
+ )
+ try:
+ verifier.verify(sig, msg, padder, self.hash_alg())
+ return True
+ except InvalidSignature:
+ return False
class OKPAlgorithm(Algorithm):
"""
@@ -151,6 +486,27 @@ if has_crypto:
def __init__(self, **kwargs: Any) -> None:
pass
+ def prepare_key(self, key: Union[str, bytes, AllowedOKPKeys]) -> AllowedOKPKeys:
+ if isinstance(key, (Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey)):
+ return key
+
+ key = force_bytes(key)
+ if is_pem_format(key):
+ try:
+ return load_pem_private_key(key, password=None, backend=default_backend())
+ except ValueError:
+ try:
+ return load_pem_public_key(key, backend=default_backend())
+ except ValueError:
+ raise InvalidKeyError('Invalid PEM format')
+ elif is_ssh_key(key):
+ try:
+ return load_ssh_public_key(key, backend=default_backend())
+ except ValueError:
+ raise InvalidKeyError('Invalid SSH key format')
+ else:
+ raise InvalidKeyError('Invalid key format')
+
def sign(self, msg: str | bytes, key: Ed25519PrivateKey | Ed448PrivateKey) -> bytes:
"""
Sign a message ``msg`` using the EdDSA private key ``key``
@@ -159,7 +515,11 @@ if has_crypto:
or :class:`.Ed448PrivateKey` isinstance
:return bytes signature: The signature, as bytes
"""
- pass
+ msg_bytes = force_bytes(msg)
+ key_obj = self.prepare_key(key)
+ if not isinstance(key_obj, (Ed25519PrivateKey, Ed448PrivateKey)):
+ raise TypeError('Key must be an Ed25519PrivateKey or Ed448PrivateKey instance')
+ return key_obj.sign(msg_bytes)
def verify(self, msg: str | bytes, key: AllowedOKPKeys, sig: str | bytes) -> bool:
"""
@@ -171,4 +531,85 @@ if has_crypto:
A private or public EdDSA key instance
:return bool verified: True if signature is valid, False if not.
"""
- pass
\ No newline at end of file
+ msg_bytes = force_bytes(msg)
+ sig_bytes = force_bytes(sig)
+ key_obj = self.prepare_key(key)
+
+ if isinstance(key_obj, (Ed25519PrivateKey, Ed448PrivateKey)):
+ verifier = key_obj.public_key()
+ else:
+ verifier = key_obj
+
+ try:
+ verifier.verify(sig_bytes, msg_bytes)
+ return True
+ except InvalidSignature:
+ return False
+
+ @staticmethod
+ def to_jwk(key_obj: AllowedOKPKeys, as_dict: bool = False) -> Union[str, JWKDict]:
+ if isinstance(key_obj, (Ed25519PrivateKey, Ed25519PublicKey)):
+ crv = 'Ed25519'
+ elif isinstance(key_obj, (Ed448PrivateKey, Ed448PublicKey)):
+ crv = 'Ed448'
+ else:
+ raise TypeError('Key must be an EdDSA key instance')
+
+ if isinstance(key_obj, (Ed25519PrivateKey, Ed448PrivateKey)):
+ private_bytes = key_obj.private_bytes(
+ encoding=Encoding.Raw,
+ format=PrivateFormat.Raw,
+ encryption_algorithm=NoEncryption()
+ )
+ public_bytes = key_obj.public_key().public_bytes(
+ encoding=Encoding.Raw,
+ format=PublicFormat.Raw
+ )
+ jwk = {
+ 'kty': 'OKP',
+ 'crv': crv,
+ 'x': base64url_encode(public_bytes).decode('ascii'),
+ 'd': base64url_encode(private_bytes).decode('ascii')
+ }
+ else:
+ public_bytes = key_obj.public_bytes(
+ encoding=Encoding.Raw,
+ format=PublicFormat.Raw
+ )
+ jwk = {
+ 'kty': 'OKP',
+ 'crv': crv,
+ 'x': base64url_encode(public_bytes).decode('ascii')
+ }
+
+ if as_dict:
+ return jwk
+ return json.dumps(jwk)
+
+ @staticmethod
+ def from_jwk(jwk: Union[str, JWKDict]) -> AllowedOKPKeys:
+ if isinstance(jwk, str):
+ jwk = json.loads(jwk)
+ if not isinstance(jwk, dict):
+ raise InvalidKeyError('Key must be a dict or a string')
+ if jwk.get('kty') != 'OKP':
+ raise InvalidKeyError('Not an OKP key')
+
+ curve = jwk.get('crv')
+ if curve not in ['Ed25519', 'Ed448']:
+ raise InvalidKeyError('Invalid curve')
+
+ x = base64url_decode(jwk['x'])
+ if 'd' in jwk:
+ # Private key
+ d = base64url_decode(jwk['d'])
+ if curve == 'Ed25519':
+ return Ed25519PrivateKey.from_private_bytes(d)
+ else:
+ return Ed448PrivateKey.from_private_bytes(d)
+ else:
+ # Public key
+ if curve == 'Ed25519':
+ return Ed25519PublicKey.from_public_bytes(x)
+ else:
+ return Ed448PublicKey.from_public_bytes(x)
\ No newline at end of file
diff --git a/jwt/api_jwk.py b/jwt/api_jwk.py
index 1d55501..89f1902 100644
--- a/jwt/api_jwk.py
+++ b/jwt/api_jwk.py
@@ -49,6 +49,34 @@ class PyJWK:
raise PyJWKError(f'Unable to find an algorithm for key: {self._jwk_data}')
self.key = self.Algorithm.from_jwk(self._jwk_data)
+ @property
+ def key_id(self) -> str | None:
+ return self._jwk_data.get('kid')
+
+ @property
+ def public_key_use(self) -> str | None:
+ return self._jwk_data.get('use')
+
+ @property
+ def key_type(self) -> str:
+ return self._jwk_data.get('kty')
+
+ @classmethod
+ def from_dict(cls, obj: JWKDict, algorithm: str | None=None) -> 'PyJWK':
+ """Creates a PyJWK from a dict object."""
+ if not isinstance(obj, dict):
+ raise InvalidKeyError('Invalid JWK format')
+ return cls(obj, algorithm)
+
+ @classmethod
+ def from_json(cls, data: str, algorithm: str | None=None) -> 'PyJWK':
+ """Creates a PyJWK from a JSON-encoded string."""
+ try:
+ obj = json.loads(data)
+ except ValueError as e:
+ raise InvalidKeyError(f'Invalid JWK format: {str(e)}')
+ return cls.from_dict(obj, algorithm)
+
class PyJWKSet:
def __init__(self, keys: list[JWKDict]) -> None:
@@ -71,8 +99,25 @@ class PyJWKSet:
return key
raise KeyError(f'keyset has no key for kid: {kid}')
+ @classmethod
+ def from_dict(cls, obj: dict[str, Any]) -> 'PyJWKSet':
+ """Creates a PyJWKSet from a dict object."""
+ if not isinstance(obj, dict):
+ raise PyJWKSetError('Invalid JWK Set value')
+ keys = obj.get('keys', [])
+ return cls(keys)
+
+ @classmethod
+ def from_json(cls, data: str) -> 'PyJWKSet':
+ """Creates a PyJWKSet from a JSON-encoded string."""
+ try:
+ obj = json.loads(data)
+ except ValueError as e:
+ raise PyJWKSetError(f'Invalid JWK Set value: {str(e)}')
+ return cls.from_dict(obj)
+
class PyJWTSetWithTimestamp:
- def __init__(self, jwk_set: PyJWKSet):
+ def __init__(self, jwk_set: PyJWKSet, timestamp: float | None=None):
self.jwk_set = jwk_set
- self.timestamp = time.monotonic()
\ No newline at end of file
+ self.timestamp = timestamp if timestamp is not None else time.monotonic()
\ No newline at end of file
diff --git a/jwt/api_jws.py b/jwt/api_jws.py
index 9a20466..84bc60d 100644
--- a/jwt/api_jws.py
+++ b/jwt/api_jws.py
@@ -23,24 +23,38 @@ class PyJWS:
options = {}
self.options = {**self._get_default_options(), **options}
+ def _get_default_options(self) -> dict[str, Any]:
+ """Returns the default options for this instance."""
+ return {
+ 'verify_signature': True
+ }
+
def register_algorithm(self, alg_id: str, alg_obj: Algorithm) -> None:
"""
Registers a new Algorithm for use when creating and verifying tokens.
"""
- pass
+ if not isinstance(alg_obj, Algorithm):
+ raise TypeError('Algorithm must be an instance of Algorithm')
+ if alg_id in self._algorithms:
+ raise ValueError(f'Algorithm {alg_id} is already registered')
+ self._algorithms[alg_id] = alg_obj
+ self._valid_algs.add(alg_id)
def unregister_algorithm(self, alg_id: str) -> None:
"""
Unregisters an Algorithm for use when creating and verifying tokens
Throws KeyError if algorithm is not registered.
"""
- pass
+ if alg_id not in self._algorithms:
+ raise KeyError(f'Algorithm {alg_id} not found')
+ del self._algorithms[alg_id]
+ self._valid_algs.remove(alg_id)
def get_algorithms(self) -> list[str]:
"""
Returns a list of supported values for the 'alg' parameter.
"""
- pass
+ return list(self._valid_algs)
def get_algorithm_by_name(self, alg_name: str) -> Algorithm:
"""
@@ -50,7 +64,9 @@ class PyJWS:
>>> jws_obj.get_algorithm_by_name("RS256")
"""
- pass
+ if alg_name not in self._algorithms:
+ raise InvalidAlgorithmError('Algorithm not supported')
+ return self._algorithms[alg_name]
def get_unverified_header(self, jwt: str | bytes) -> dict[str, Any]:
"""Returns back the JWT header parameters as a dict()
@@ -58,7 +74,277 @@ class PyJWS:
Note: The signature is not verified so the header parameters
should not be fully trusted until signature verification is complete
"""
- pass
+ if not isinstance(jwt, (str, bytes)):
+ raise InvalidTokenError('Invalid token type')
+
+ if isinstance(jwt, str):
+ jwt = jwt.encode('utf-8')
+
+ try:
+ signing_input, crypto_segment = jwt.rsplit(b'.', 1)
+ header_segment, payload_segment = signing_input.split(b'.', 1)
+ except ValueError:
+ raise InvalidTokenError('Not enough segments')
+
+ try:
+ header_data = base64url_decode(header_segment)
+ except (TypeError, binascii.Error):
+ raise DecodeError('Invalid header padding')
+
+ try:
+ header = json.loads(header_data.decode('utf-8'))
+ except ValueError as e:
+ raise DecodeError('Invalid header string: %s' % e)
+
+ if not isinstance(header, dict):
+ raise DecodeError('Invalid header string: must be a json object')
+
+ return header
+
+ def encode(self, payload: bytes, key: str | bytes | AllowedPrivateKeys | None=None, algorithm: str | None=None, headers: dict[str, Any] | None=None, json_encoder: type[json.JSONEncoder] | None=None, is_payload_detached: bool=False, sort_headers: bool=False) -> str:
+ """Creates a JWT using the given algorithm.
+
+ Args:
+ payload: The claims content to sign
+ key: The key to use for signing the claim. Note: if the algorithm is None, the key is not used
+ algorithm: The signing algorithm to use. If none is specified then 'none' is used.
+ headers: A dict of additional headers to use.
+ json_encoder: A custom JSON encoder to use for encoding the JWT.
+ is_payload_detached: If True, the payload will be detached from the JWS.
+ sort_headers: If True, sort the header keys.
+ """
+ # Check that we have a mapping
+ if not isinstance(payload, bytes):
+ raise TypeError('Payload must be bytes')
+
+ if algorithm is None:
+ algorithm = 'none'
+
+ if algorithm not in self._valid_algs:
+ raise InvalidAlgorithmError('Algorithm not supported')
+
+ if algorithm != 'none' and key is None:
+ raise InvalidKeyError('Key is required when algorithm is not "none"')
+
+ # Header
+ header = {'alg': algorithm}
+ if self.header_typ is not None and 'typ' not in (headers or {}):
+ header['typ'] = self.header_typ
+
+ if headers:
+ header.update(headers)
+ if header.get('typ') == '':
+ del header['typ']
+ elif header.get('typ') is None:
+ del header['typ']
+
+ if is_payload_detached:
+ header['b64'] = False
+ if not payload:
+ raise InvalidTokenError('Payload cannot be empty when using detached content')
+
+ if sort_headers:
+ header = dict(sorted(header.items()))
+
+ json_header = json.dumps(header, separators=(',', ':'), cls=json_encoder).encode('utf-8')
+ header_input = base64url_encode(json_header)
+
+ if is_payload_detached:
+ payload_input = b''
+ else:
+ payload_input = base64url_encode(payload)
+
+ signing_input = b'.'.join([header_input, payload_input])
+
+ try:
+ alg_obj = self._algorithms[algorithm]
+ if algorithm == 'none':
+ key = None
+ elif key is None:
+ raise TypeError('Key is required when algorithm is not "none"')
+ else:
+ key = alg_obj.prepare_key(key)
+ signature = alg_obj.sign(signing_input if not is_payload_detached else payload, key)
+ except Exception as e:
+ raise TypeError('Unable to encode JWT: %s' % e)
+
+ encoded_signature = base64url_encode(signature)
+ encoded_jwt = b'.'.join([signing_input, encoded_signature])
+
+ return encoded_jwt.decode('utf-8')
+
+ def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> dict[str, Any]:
+ """Decodes a JWT and returns a dict of the token contents.
+
+ Args:
+ jwt: The JWT to decode.
+ key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
+ algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
+ options: A dict of options for decoding. If None, use default options.
+ detached_payload: The detached payload to use for verification.
+ **kwargs: Additional options for decoding.
+
+ Returns:
+ A dict including:
+ - header: A dict of the JWT header
+ - payload: The decoded payload
+ - signature: The signature of the JWT
+ """
+ deprecated_kwargs = {
+ 'verify': 'verify_signature',
+ 'verify_exp': 'verify_exp',
+ 'verify_iat': 'verify_iat',
+ 'verify_nbf': 'verify_nbf',
+ 'verify_aud': 'verify_aud',
+ 'verify_iss': 'verify_iss',
+ }
+
+ options = options or {}
+ for old_name, new_name in deprecated_kwargs.items():
+ if old_name in kwargs:
+ warnings.warn(
+ f'The {old_name} parameter is deprecated. '
+ f'Please use {new_name} in options instead.',
+ category=DeprecationWarning,
+ stacklevel=2
+ )
+ options[new_name] = kwargs.pop(old_name)
+
+ for kwarg in kwargs:
+ warnings.warn(
+ f'The "{kwarg}" argument is not supported and will be ignored.',
+ category=RemovedInPyjwt3Warning,
+ stacklevel=2
+ )
+
+ merged_options = {**self.options}
+ if options:
+ if not isinstance(options, dict):
+ raise TypeError('options must be a dict')
+ merged_options.update(options)
+
+ if isinstance(jwt, str):
+ jwt = jwt.encode('utf-8')
+
+ if not isinstance(jwt, bytes):
+ raise DecodeError('Invalid token type')
+
+ try:
+ signing_input, crypto_segment = jwt.rsplit(b'.', 1)
+ header_segment, payload_segment = signing_input.split(b'.', 1)
+ except ValueError:
+ raise InvalidTokenError('Not enough segments')
+
+ try:
+ header_data = base64url_decode(header_segment)
+ except (TypeError, binascii.Error):
+ raise DecodeError('Invalid header padding')
+
+ try:
+ header = json.loads(header_data.decode('utf-8'))
+ except ValueError as e:
+ raise DecodeError('Invalid header string: %s' % e)
+
+ if not isinstance(header, dict):
+ raise DecodeError('Invalid header string: must be a json object')
+
+ if header.get('b64', True):
+ try:
+ payload = base64url_decode(payload_segment)
+ except (TypeError, binascii.Error):
+ raise DecodeError('Invalid payload padding')
+ else:
+ if detached_payload is None:
+ raise DecodeError('It is required that you pass in a value for the "detached_payload" argument to decode a message using unencoded payload.')
+ payload = detached_payload
+
+ try:
+ signature = base64url_decode(crypto_segment)
+ except (TypeError, binascii.Error):
+ raise DecodeError('Invalid crypto padding')
+
+ if algorithms is None:
+ algorithms = list(self._valid_algs)
+
+ if not algorithms and merged_options['verify_signature']:
+ raise DecodeError('No algorithms were specified')
+
+ try:
+ alg = header['alg']
+ except KeyError:
+ raise InvalidTokenError('Missing algorithm ("alg") in headers')
+
+ if alg not in algorithms:
+ raise InvalidAlgorithmError('The specified alg value is not allowed')
+
+ if alg == 'none':
+ if merged_options['verify_signature']:
+ raise DecodeError('Algorithm "none" not allowed')
+ if key not in [None, '', 'none']:
+ raise InvalidKeyError('When alg = "none", key must be empty or "none"')
+ if signature != b'':
+ raise InvalidSignatureError('Signature verification failed')
+ return {
+ 'header': header,
+ 'payload': payload,
+ 'signature': signature
+ }
+
+ try:
+ alg_obj = self._algorithms[alg]
+ except KeyError:
+ raise InvalidAlgorithmError('Algorithm not supported')
+
+ if merged_options['verify_signature']:
+ try:
+ if key is None:
+ raise InvalidKeyError('Key is required when algorithm is not "none"')
+ key = alg_obj.prepare_key(key)
+ except InvalidKeyError:
+ raise
+ except Exception as e:
+ raise InvalidTokenError('Unable to parse signature key: %s' % e)
+
+ try:
+ if not alg_obj.verify(signing_input if header.get('b64', True) else payload, key, signature):
+ raise InvalidSignatureError('Signature verification failed')
+ except Exception as e:
+ raise InvalidSignatureError('Signature verification failed: %s' % e)
+ elif key is not None and key not in [None, '', 'none']:
+ try:
+ key = alg_obj.prepare_key(key)
+ except Exception:
+ pass
+
+ if not algorithms and not merged_options['verify_signature']:
+ warnings.warn(
+ 'It is required that you pass in a value for the "algorithms" argument when calling decode(). '
+ 'This argument will be mandatory in a future version.',
+ category=DeprecationWarning,
+ stacklevel=2
+ )
+
+ if not merged_options['verify_signature'] and not algorithms:
+ warnings.warn(
+ 'The "algorithms" argument is not optional when "verify_signature" is False. '
+ 'This argument will be mandatory in a future version.',
+ category=DeprecationWarning,
+ stacklevel=2
+ )
+
+ return {
+ 'header': header,
+ 'payload': payload,
+ 'signature': signature
+ }
+
+ def decode(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, detached_payload: bytes | None=None, **kwargs: Any) -> bytes:
+ """Decodes a JWT and returns the payload.
+
+ This is a shortcut to :meth:`decode_complete()` that returns just the payload.
+ """
+ decoded = self.decode_complete(jwt, key, algorithms, options, detached_payload, **kwargs)
+ return decoded['payload']
_jws_global_obj = PyJWS()
encode = _jws_global_obj.encode
decode_complete = _jws_global_obj.decode_complete
diff --git a/jwt/api_jwt.py b/jwt/api_jwt.py
index a61eb5f..75fdc2e 100644
--- a/jwt/api_jwt.py
+++ b/jwt/api_jwt.py
@@ -18,6 +18,18 @@ class PyJWT:
options = {}
self.options: dict[str, Any] = {**self._get_default_options(), **options}
+ def _get_default_options(self) -> dict[str, Any]:
+ """Returns the default options for this instance."""
+ return {
+ 'verify_signature': True,
+ 'verify_exp': True,
+ 'verify_nbf': True,
+ 'verify_iat': True,
+ 'verify_aud': True,
+ 'verify_iss': True,
+ 'require': []
+ }
+
def _encode_payload(self, payload: dict[str, Any], headers: dict[str, Any] | None=None, json_encoder: type[json.JSONEncoder] | None=None) -> bytes:
"""
Encode a given payload to the bytes to be signed.
@@ -25,7 +37,8 @@ class PyJWT:
This method is intended to be overridden by subclasses that need to
encode the payload in a different way, e.g. compress the payload.
"""
- pass
+ json_str = json.dumps(payload, separators=(',', ':'), cls=json_encoder).encode('utf-8')
+ return json_str
def _decode_payload(self, decoded: dict[str, Any]) -> Any:
"""
@@ -35,7 +48,127 @@ class PyJWT:
decode the payload in a different way, e.g. decompress compressed
payloads.
"""
- pass
+ try:
+ payload = json.loads(decoded['payload'].decode('utf-8'))
+ except ValueError as e:
+ raise DecodeError('Invalid payload string: %s' % e)
+ if not isinstance(payload, dict):
+ raise DecodeError('Invalid payload string: must be a json object')
+ return payload
+
+ def encode(self, payload: dict[str, Any], key: str | bytes | AllowedPrivateKeys | None=None, algorithm: str | None=None, headers: dict[str, Any] | None=None, json_encoder: type[json.JSONEncoder] | None=None) -> str:
+ """
+ Encode a JWT from a payload and optional headers.
+
+ Takes a payload and signs it using the specified algorithm.
+
+ Arguments:
+ payload: A dict of claims for the JWT.
+ key: The key to use for signing the claim. Note: if the algorithm is None, the key is not used.
+ algorithm: The signing algorithm to use. If none is specified then 'none' is used.
+ headers: A dict of additional headers to use.
+ json_encoder: A custom JSON encoder to use for encoding the JWT.
+ """
+ # Check that we have a mapping
+ if not isinstance(payload, dict):
+ raise TypeError('Payload must be a dict')
+
+ # Add reserved claims
+ for time_claim in ['exp', 'iat', 'nbf']:
+ if time_claim in payload:
+ value = payload[time_claim]
+ if isinstance(value, datetime):
+ payload[time_claim] = timegm(value.utctimetuple())
+ elif isinstance(value, str):
+ try:
+ payload[time_claim] = int(value)
+ except ValueError:
+ raise TypeError(f'{time_claim} must be a valid timestamp')
+ elif not isinstance(value, (int, float)):
+ raise TypeError(f'{time_claim} must be a valid timestamp')
+
+ json_payload = self._encode_payload(payload, headers, json_encoder)
+ return api_jws.encode(json_payload, key, algorithm, headers, json_encoder)
+
+ def decode_complete(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, **kwargs: Any) -> dict[str, Any]:
+ """
+ Decodes a JWT and returns a dict of the token contents.
+
+ Args:
+ jwt: The JWT to decode.
+ key: The key to use for verifying the claim. Note: if the algorithm is 'none', the key is not used.
+ algorithms: A list of allowed algorithms. If None, default to the algorithms registered.
+ options: A dict of options for decoding. If None, use default options.
+ **kwargs: Additional options for decoding.
+
+ Returns:
+ A dict including:
+ - header: A dict of the JWT header
+ - payload: The decoded payload
+ - signature: The signature of the JWT
+ """
+ merged_options = {**self.options, **(options or {})}
+ decoded = api_jws.decode_complete(jwt, key, algorithms, merged_options)
+ payload = self._decode_payload(decoded)
+
+ if merged_options['verify_exp'] and 'exp' in payload:
+ now = kwargs.get('now', datetime.now(timezone.utc))
+ exp = datetime.fromtimestamp(payload['exp'], tz=timezone.utc)
+ leeway = timedelta(seconds=kwargs.get('leeway', 0))
+ if now > exp + leeway:
+ raise ExpiredSignatureError('Signature has expired')
+
+ if merged_options['verify_nbf'] and 'nbf' in payload:
+ now = kwargs.get('now', datetime.now(timezone.utc))
+ nbf = datetime.fromtimestamp(payload['nbf'], tz=timezone.utc)
+ leeway = timedelta(seconds=kwargs.get('leeway', 0))
+ if now < nbf - leeway:
+ raise ImmatureSignatureError('The token is not yet valid (nbf)')
+
+ if merged_options['verify_iat'] and 'iat' in payload:
+ now = kwargs.get('now', datetime.now(timezone.utc))
+ iat = datetime.fromtimestamp(payload['iat'], tz=timezone.utc)
+ leeway = timedelta(seconds=kwargs.get('leeway', 0))
+ if now < iat - leeway:
+ raise InvalidIssuedAtError('Issued at claim (iat) cannot be in the future')
+
+ if merged_options['verify_iss']:
+ expected_issuer = kwargs.get('issuer', None)
+ if expected_issuer is not None:
+ if 'iss' not in payload:
+ raise MissingRequiredClaimError('Issuer claim expected but not present')
+ if payload['iss'] != expected_issuer:
+ raise InvalidIssuerError('Invalid issuer')
+
+ if merged_options['verify_aud']:
+ expected_audience = kwargs.get('audience', None)
+ if expected_audience is not None:
+ if 'aud' not in payload:
+ raise MissingRequiredClaimError('Audience claim expected but not present')
+ audience = payload['aud']
+ if isinstance(audience, str):
+ audience = [audience]
+ if not isinstance(audience, Iterable) or not all(isinstance(aud, str) for aud in audience):
+ raise InvalidAudienceError('Invalid claim format in token')
+ if expected_audience not in audience:
+ raise InvalidAudienceError('Invalid audience')
+
+ if merged_options['require']:
+ for claim in merged_options['require']:
+ if claim not in payload:
+ raise MissingRequiredClaimError(f'Token is missing the "{claim}" claim')
+
+ decoded['payload'] = payload
+ return decoded
+
+ def decode(self, jwt: str | bytes, key: str | bytes | AllowedPublicKeys | None=None, algorithms: list[str] | None=None, options: dict[str, Any] | None=None, **kwargs: Any) -> dict[str, Any]:
+ """
+ Decodes a JWT and returns the payload.
+
+ This is a shortcut to :meth:`decode_complete()` that returns just the payload.
+ """
+ decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
+ return decoded['payload']
_jwt_global_obj = PyJWT()
encode = _jwt_global_obj.encode
decode_complete = _jwt_global_obj.decode_complete
diff --git a/jwt/jwk_set_cache.py b/jwt/jwk_set_cache.py
index e57abb8..87cba70 100644
--- a/jwt/jwk_set_cache.py
+++ b/jwt/jwk_set_cache.py
@@ -6,4 +6,22 @@ class JWKSetCache:
def __init__(self, lifespan: int) -> None:
self.jwk_set_with_timestamp: Optional[PyJWTSetWithTimestamp] = None
- self.lifespan = lifespan
\ No newline at end of file
+ self.lifespan = lifespan
+
+ @property
+ def jwk_set(self) -> PyJWKSet:
+ if self.jwk_set_with_timestamp is None:
+ raise ValueError('No JWK set has been cached')
+ return self.jwk_set_with_timestamp.jwk_set
+
+ @jwk_set.setter
+ def jwk_set(self, value: PyJWKSet) -> None:
+ self.jwk_set_with_timestamp = PyJWTSetWithTimestamp(value, int(time.time()))
+
+ def is_expired(self) -> bool:
+ if self.jwk_set_with_timestamp is None:
+ return True
+ return int(time.time()) - self.jwk_set_with_timestamp.timestamp > self.lifespan
+
+ def delete(self) -> None:
+ self.jwk_set_with_timestamp = None
\ No newline at end of file
diff --git a/jwt/jwks_client.py b/jwt/jwks_client.py
index 34c461f..a717851 100644
--- a/jwt/jwks_client.py
+++ b/jwt/jwks_client.py
@@ -26,4 +26,101 @@ class PyJWKClient:
else:
self.jwk_set_cache = None
if cache_keys:
- self.get_signing_key = lru_cache(maxsize=max_cached_keys)(self.get_signing_key)
\ No newline at end of file
+ self.get_signing_key = lru_cache(maxsize=max_cached_keys)(self.get_signing_key)
+
+ def fetch_data(self) -> str:
+ """Fetch the JWKS data from the uri provided during instantiation."""
+ try:
+ request = urllib.request.Request(self.uri, headers=self.headers)
+ if self.ssl_context:
+ response = urllib.request.urlopen(request, timeout=self.timeout, context=self.ssl_context)
+ else:
+ response = urllib.request.urlopen(request, timeout=self.timeout)
+ data = response.read()
+ if isinstance(data, str):
+ return data
+ return data.decode('utf-8')
+ except URLError as e:
+ raise PyJWKClientConnectionError(f'Failed to fetch JWKS from {self.uri}. Error: {str(e)}')
+
+ def get_jwk_set(self, refresh: bool=False) -> PyJWKSet:
+ """Return the fetched PyJWKSet.
+
+ Args:
+ refresh: Force a refetch of the JWKS.
+ """
+ if not refresh and self.jwk_set_cache and not self.jwk_set_cache.is_expired():
+ return self.jwk_set_cache.jwk_set
+
+ data = self.fetch_data()
+ try:
+ jwk_set = PyJWKSet.from_json(data)
+ except Exception as e:
+ if self.jwk_set_cache:
+ self.jwk_set_cache.delete()
+ raise PyJWKClientError(f'Failed to parse JWKS: {str(e)}')
+
+ if self.jwk_set_cache:
+ self.jwk_set_cache.jwk_set = jwk_set
+
+ return jwk_set
+
+ def get_signing_keys(self) -> List[PyJWK]:
+ """Return a list of signing keys from the JWKS."""
+ jwk_set = self.get_jwk_set()
+ signing_keys = []
+
+ for jwk_key in jwk_set.keys:
+ if jwk_key.public_key_use == 'sig' or not jwk_key.public_key_use:
+ signing_keys.append(jwk_key)
+
+ if not signing_keys:
+ raise PyJWKClientError('No signing keys found in JWKS')
+
+ return signing_keys
+
+ def get_signing_key(self, kid: str) -> PyJWK:
+ """Return the signing key from the JWKS that matches the provided kid.
+
+ Args:
+ kid: The key ID to search for.
+ """
+ signing_keys = self.get_signing_keys()
+ for key in signing_keys:
+ if key.key_id == kid:
+ return key
+
+ # If no key is found, try refreshing the JWKS once
+ signing_keys = self.get_signing_keys()
+ for key in signing_keys:
+ if key.key_id == kid:
+ return key
+
+ raise PyJWKClientError(f'Unable to find a signing key that matches: {kid}')
+
+ def get_signing_key_from_jwt(self, token: str, refresh_jwks: bool=True) -> PyJWK:
+ """Return the signing key from the JWKS that matches the kid in the token header.
+
+ Args:
+ token: The JWT token to get the key for.
+ refresh_jwks: Whether to refresh the JWKS if the key is not found.
+ """
+ try:
+ headers = decode_token(token, options={'verify_signature': False})['header']
+ except Exception as e:
+ raise PyJWKClientError(f'Failed to decode JWT headers: {str(e)}')
+
+ kid = headers.get('kid')
+ if not kid:
+ signing_keys = self.get_signing_keys()
+ if len(signing_keys) == 1:
+ return signing_keys[0]
+ raise PyJWKClientError('Token headers must include a key ID ("kid")')
+
+ try:
+ return self.get_signing_key(kid)
+ except PyJWKClientError:
+ if refresh_jwks:
+ self.get_jwk_set(refresh=True)
+ return self.get_signing_key(kid)
+ raise
\ No newline at end of file
diff --git a/jwt/utils.py b/jwt/utils.py
index 3115d4c..6b01bd9 100644
--- a/jwt/utils.py
+++ b/jwt/utils.py
@@ -7,6 +7,74 @@ try:
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature
except ModuleNotFoundError:
pass
+
+def force_bytes(value: Union[str, bytes]) -> bytes:
+ if isinstance(value, str):
+ return value.encode('utf-8')
+ elif isinstance(value, bytes):
+ return value
+ else:
+ raise TypeError('Expected string or bytes type')
+
+def base64url_decode(input: Union[str, bytes]) -> bytes:
+ if isinstance(input, str):
+ input = input.encode('ascii')
+
+ rem = len(input) % 4
+ if rem > 0:
+ input += b'=' * (4 - rem)
+
+ return base64.urlsafe_b64decode(input)
+
+def base64url_encode(input: bytes) -> bytes:
+ return base64.urlsafe_b64encode(input).rstrip(b'=')
+
+def to_base64url_uint(val: int) -> bytes:
+ if val < 0:
+ raise ValueError('Must be a positive integer')
+
+ if val == 0:
+ return b'AA'
+
+ int_bytes = val.to_bytes((val.bit_length() + 7) // 8, byteorder='big')
+ return base64url_encode(int_bytes)
+
+def from_base64url_uint(val: Union[str, bytes]) -> int:
+ if isinstance(val, str):
+ val = val.encode('ascii')
+
+ data = base64url_decode(val)
+ return int.from_bytes(data, byteorder='big')
+
+def der_to_raw_signature(der_sig: bytes, curve: EllipticCurve) -> bytes:
+ r, s = decode_dss_signature(der_sig)
+ key_size = (curve.key_size + 7) // 8
+ return r.to_bytes(key_size, byteorder='big') + s.to_bytes(key_size, byteorder='big')
+
+def raw_to_der_signature(raw_sig: bytes, curve: EllipticCurve) -> bytes:
+ key_size = (curve.key_size + 7) // 8
+ if len(raw_sig) != 2 * key_size:
+ raise ValueError('Invalid signature')
+
+ r = int.from_bytes(raw_sig[:key_size], byteorder='big')
+ s = int.from_bytes(raw_sig[key_size:], byteorder='big')
+ return encode_dss_signature(r, s)
+
+def is_pem_format(key: bytes) -> bool:
+ return bool(_PEM_RE.search(key))
+
+def is_ssh_key(key: bytes) -> bool:
+ if key.startswith(b'ssh-') or key.startswith(b'ecdsa-'):
+ return True
+
+ match = _SSH_PUBKEY_RC.match(key)
+ if not match:
+ return False
+
+ key_type = match.group(1)
+ return (key_type in _SSH_KEY_FORMATS or
+ any(key_type.endswith(suffix) for suffix in [_CERT_SUFFIX]))
+
_PEMS = {b'CERTIFICATE', b'TRUSTED CERTIFICATE', b'PRIVATE KEY', b'PUBLIC KEY', b'ENCRYPTED PRIVATE KEY', b'OPENSSH PRIVATE KEY', b'DSA PRIVATE KEY', b'RSA PRIVATE KEY', b'RSA PUBLIC KEY', b'EC PRIVATE KEY', b'DH PARAMETERS', b'NEW CERTIFICATE REQUEST', b'CERTIFICATE REQUEST', b'SSH2 PUBLIC KEY', b'SSH2 ENCRYPTED PRIVATE KEY', b'X509 CRL'}
_PEM_RE = re.compile(b'----[- ]BEGIN (' + b'|'.join(_PEMS) + b')[- ]----\r?\n.+?\r?\n----[- ]END \\1[- ]----\r?\n?', re.DOTALL)
_CERT_SUFFIX = b'-cert-v01@openssh.com'