119 tests/test_advisory.py
  1102 tests/test_algorithms.py
   298 tests/test_api_jwk.py
   817 tests/test_api_jws.py
   804 tests/test_api_jwt.py
    37 tests/test_compressed_jwt.py
     7 tests/test_exceptions.py
   357 tests/test_jwks_client.py
    19 tests/test_jwt.py
    39 tests/test_utils.py
  3599 total
import json
from decimal import Decimal

import pytest

from jwt.algorithms import NoneAlgorithm, has_crypto
from jwt.api_jws import PyJWS
from jwt.exceptions import (
    DecodeError,
    InvalidAlgorithmError,
    InvalidSignatureError,
    InvalidTokenError,
)
from jwt.utils import base64url_decode
from jwt.warnings import RemovedInPyjwt3Warning

from .utils import crypto_required, key_path, no_crypto_required

try:
    from cryptography.hazmat.primitives.serialization import (
        load_pem_private_key,
        load_pem_public_key,
        load_ssh_public_key,
    )
except ModuleNotFoundError:
    pass


@pytest.fixture
def jws():
    return PyJWS()


@pytest.fixture
def payload():
    """Creates a sample jws claimset for use as a payload during tests"""
    return b"hello world"


class TestJWS:
    def test_register_algo_does_not_allow_duplicate_registration(self, jws):
        jws.register_algorithm("AAA", NoneAlgorithm())

        with pytest.raises(ValueError):
            jws.register_algorithm("AAA", NoneAlgorithm())

    def test_register_algo_rejects_non_algorithm_obj(self, jws):
        with pytest.raises(TypeError):
            jws.register_algorithm("AAA123", {})

    def test_unregister_algo_removes_algorithm(self, jws):
        supported = jws.get_algorithms()
        assert "none" in supported
        assert "HS256" in supported

        jws.unregister_algorithm("HS256")

        supported = jws.get_algorithms()
        assert "HS256" not in supported

    def test_unregister_algo_throws_error_if_not_registered(self, jws):
        with pytest.raises(KeyError):
            jws.unregister_algorithm("AAA")

    def test_algo_parameter_removes_alg_from_algorithms_list(self, jws):
        assert "none" in jws.get_algorithms()
        assert "HS256" in jws.get_algorithms()

        jws = PyJWS(algorithms=["HS256"])
        assert "none" not in jws.get_algorithms()
        assert "HS256" in jws.get_algorithms()

    def test_override_options(self):
        jws = PyJWS(options={"verify_signature": False})

        assert not jws.options["verify_signature"]

    def test_non_object_options_dont_persist(self, jws, payload):
        token = jws.encode(payload, "secret")

        jws.decode(token, "secret", options={"verify_signature": False})

        assert jws.options["verify_signature"]

    def test_options_must_be_dict(self):
        pytest.raises(TypeError, PyJWS, options=object())
        pytest.raises((TypeError, ValueError), PyJWS, options=("something"))

    def test_encode_decode(self, jws, payload):
        secret = "secret"
        jws_message = jws.encode(payload, secret, algorithm="HS256")
        decoded_payload = jws.decode(jws_message, secret, algorithms=["HS256"])

        assert decoded_payload == payload

    def test_decode_fails_when_alg_is_not_on_method_algorithms_param(
        self, jws, payload
    ):
        secret = "secret"
        jws_token = jws.encode(payload, secret, algorithm="HS256")
        jws.decode(jws_token, secret, algorithms=["HS256"])

        with pytest.raises(InvalidAlgorithmError):
            jws.decode(jws_token, secret, algorithms=["HS384"])

    def test_decode_works_with_unicode_token(self, jws):
        secret = "secret"
        unicode_jws = (
            "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9"
            ".eyJoZWxsbyI6ICJ3b3JsZCJ9"
            ".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8"
        )

        jws.decode(unicode_jws, secret, algorithms=["HS256"])

    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"])

        exception = context.value
        assert str(exception) == "Not enough segments"

    def test_decode_invalid_token_type_is_none(self, jws):
        example_jws = None
        example_secret = "secret"

        with pytest.raises(DecodeError) as context:
            jws.decode(example_jws, example_secret, algorithms=["HS256"])

        exception = context.value
        assert "Invalid token type" in str(exception)

    def test_decode_invalid_token_type_is_int(self, jws):
        example_jws = 123
        example_secret = "secret"

        with pytest.raises(DecodeError) as context:
            jws.decode(example_jws, example_secret, algorithms=["HS256"])

        exception = context.value
        assert "Invalid token type" in str(exception)

    def test_decode_with_non_mapping_header_throws_exception(self, jws):
        secret = "secret"
        example_jws = (
            "MQ"  # == 1
            ".eyJoZWxsbyI6ICJ3b3JsZCJ9"
            ".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8"
        )

        with pytest.raises(DecodeError) as context:
            jws.decode(example_jws, secret, algorithms=["HS256"])

        exception = context.value
        assert str(exception) == "Invalid header string: must be a json object"

    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")

        exception = context.value
        assert str(exception) == "Algorithm not supported"

    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"

    @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"])

    @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"})
        assert b"hello world" == jws.decode(msg, pub_key, algorithms=["ES256"])

    def test_decode_algorithm_param_should_be_case_sensitive(self, jws):
        example_jws = (
            "eyJhbGciOiJoczI1NiIsInR5cCI6IkpXVCJ9"  # alg = hs256
            ".eyJoZWxsbyI6IndvcmxkIn0"
            ".5R_FEPE7SW2dT9GgIxPgZATjFGXfUDOSwo7TtO_Kd_g"
        )

        with pytest.raises(InvalidAlgorithmError) as context:
            jws.decode(example_jws, "secret", algorithms=["hs256"])

        exception = context.value
        assert str(exception) == "Algorithm not supported"

    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"])
        assert "Signature verification failed" == str(excinfo.value)

        with pytest.raises(InvalidSignatureError) as excinfo:
            jws.decode(jws_message, bad_secret, algorithms=["HS256"])
        assert "Signature verification failed" == str(excinfo.value)

    def test_decodes_valid_jws(self, jws, payload):
        example_secret = "secret"
        example_jws = (
            b"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9."
            b"aGVsbG8gd29ybGQ."
            b"gEW0pdU4kxPthjtehYdhxB9mMOGajt1xCKlGGXDJ8PM"
        )

        decoded_payload = jws.decode(example_jws, example_secret, algorithms=["HS256"])

        assert decoded_payload == payload

    def test_decodes_complete_valid_jws(self, jws, payload):
        example_secret = "secret"
        example_jws = (
            b"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9."
            b"aGVsbG8gd29ybGQ."
            b"gEW0pdU4kxPthjtehYdhxB9mMOGajt1xCKlGGXDJ8PM"
        )

        decoded = jws.decode_complete(example_jws, example_secret, algorithms=["HS256"])

        assert decoded == {
            "header": {"alg": "HS256", "typ": "JWT"},
            "payload": payload,
            "signature": (
                b"\x80E\xb4\xa5\xd58\x93\x13\xed\x86;^\x85\x87a\xc4"
                b"\x1ff0\xe1\x9a\x8e\xddq\x08\xa9F\x19p\xc9\xf0\xf3"
            ),
        }

    # 'Control' Elliptic Curve jws created by another library.
    # Used to test for regressions that could affect both
    # encoding / decoding operations equally (causing tests
    # to still pass).
    @crypto_required
    def test_decodes_valid_es384_jws(self, jws):
        example_payload = {"hello": "world"}
        with open(key_path("testkey_ec.pub")) as fp:
            example_pubkey = fp.read()
        example_jws = (
            b"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9."
            b"eyJoZWxsbyI6IndvcmxkIn0.TORyNQab_MoXM7DvNKaTwbrJr4UY"
            b"d2SsX8hhlnWelQFmPFSf_JzC2EbLnar92t-bXsDovzxp25ExazrVHkfPkQ"
        )
        decoded_payload = jws.decode(example_jws, example_pubkey, algorithms=["ES256"])
        json_payload = json.loads(decoded_payload)

        assert json_payload == example_payload

    # 'Control' RSA jws created by another library.
    # Used to test for regressions that could affect both
    # encoding / decoding operations equally (causing tests
    # to still pass).
    @crypto_required
    def test_decodes_valid_rs384_jws(self, jws):
        example_payload = {"hello": "world"}
        with open(key_path("testkey_rsa.pub")) as fp:
            example_pubkey = fp.read()
        example_jws = (
            b"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9"
            b".eyJoZWxsbyI6IndvcmxkIn0"
            b".yNQ3nI9vEDs7lEh-Cp81McPuiQ4ZRv6FL4evTYYAh1X"
            b"lRTTR3Cz8pPA9Stgso8Ra9xGB4X3rlra1c8Jz10nTUju"
            b"O06OMm7oXdrnxp1KIiAJDerWHkQ7l3dlizIk1bmMA457"
            b"W2fNzNfHViuED5ISM081dgf_a71qBwJ_yShMMrSOfxDx"
            b"mX9c4DjRogRJG8SM5PvpLqI_Cm9iQPGMvmYK7gzcq2cJ"
            b"urHRJDJHTqIdpLWXkY7zVikeen6FhuGyn060Dz9gYq9t"
            b"uwmrtSWCBUjiN8sqJ00CDgycxKqHfUndZbEAOjcCAhBr"
            b"qWW3mSVivUfubsYbwUdUG3fSRPjaUPcpe8A"
        )
        decoded_payload = jws.decode(example_jws, example_pubkey, algorithms=["RS384"])
        json_payload = json.loads(decoded_payload)

        assert json_payload == example_payload

    def test_load_verify_valid_jws(self, jws, payload):
        example_secret = "secret"
        example_jws = (
            b"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
            b"aGVsbG8gd29ybGQ."
            b"SIr03zM64awWRdPrAM_61QWsZchAtgDV3pphfHPPWkI"
        )

        decoded_payload = jws.decode(
            example_jws, key=example_secret, algorithms=["HS256"]
        )
        assert decoded_payload == payload

    def test_allow_skip_verification(self, jws, payload):
        right_secret = "foo"
        jws_message = jws.encode(payload, right_secret)
        decoded_payload = jws.decode(jws_message, options={"verify_signature": False})

        assert decoded_payload == payload

    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:
            jws.decode(example_jws, key=example_secret)

        assert (
            'It is required that you pass in a value for the "algorithms" argument when calling decode().'
            in str(exc.value)
        )

    def test_decode_no_algorithms_verify_signature_false(self, jws):
        example_secret = "secret"
        example_jws = (
            b"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
            b"aGVsbG8gd29ybGQ."
            b"SIr03zM64awWRdPrAM_61QWsZchAtgDV3pphfHPPWkI"
        )

        jws.decode(
            example_jws,
            key=example_secret,
            options={"verify_signature": False},
        )

    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},
        )

        assert decoded_payload == payload

    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"])

    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"])

        assert "Signature verification" in str(exc.value)

    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"])

    def test_invalid_crypto_alg(self, jws, payload):
        with pytest.raises(NotImplementedError):
            jws.encode(payload, "secret", algorithm="HS1024")

    @no_crypto_required
    def test_missing_crypto_library_better_error_messages(self, jws, payload):
        with pytest.raises(NotImplementedError) as excinfo:
            jws.encode(payload, "secret", algorithm="RS256")
            assert "cryptography" in str(excinfo.value)

    def test_unicode_secret(self, jws, payload):
        secret = "\xc2"
        jws_message = jws.encode(payload, secret)
        decoded_payload = jws.decode(jws_message, secret, algorithms=["HS256"])

        assert decoded_payload == payload

    def test_nonascii_secret(self, jws, payload):
        secret = "\xc2"  # char value that ascii codec canno<response clipped><NOTE>Due to the max output limit, only part of the full response has been shown to you.</NOTE>assert decoded_payload == example_payload

    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"])

    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"])

    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},
        )

    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},
        )

    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"]
            )
            assert decoded == payload

        # With 1 seconds, should fail
        for leeway in (1, timedelta(seconds=1)):
            with pytest.raises(ExpiredSignatureError):
                jwt.decode(jwt_message, secret, leeway=leeway, algorithms=["HS256"])

    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"])

        with pytest.raises(ImmatureSignatureError):
            jwt.decode(jwt_message, secret, leeway=1, algorithms=["HS256"])

    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"])

    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"],
        )

    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"])

    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"],
            )

    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"])

    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"])

    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"]
            )

    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"])

    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"])

        assert exc.value.claim == "iss"

    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"])

        assert exc.value.claim == "aud"

    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"])

        assert exc.value.claim == "aud"

    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"])

    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"])

    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"],
        )

    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"],
        )

    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"],
            )

        assert exc.value.claim == "exp"

    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"],
            )

        assert exc.value.claim == "iat"

    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"],
            )

        assert exc.value.claim == "nbf"

    def test_skip_check_signature(self, jwt):
        token = (
            "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
            ".eyJzb21lIjoicGF5bG9hZCJ9"
            ".4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZA"
        )
        jwt.decode(
            token,
            "secret",
            options={"verify_signature": False},
            algorithms=["HS256"],
        )

    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"],
        )

    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"],
        )

    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"])

        assert payload == {"some_decimal": "it worked"}

    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},
        )

        with pytest.raises(ExpiredSignatureError):
            jwt.decode(
                jwt_message,
                secret,
                algorithms=["HS256"],
                options={"verify_exp": True},
            )

    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},
        )

        with pytest.raises(ExpiredSignatureError):
            jwt.decode(
                jwt_message,
                options={"verify_signature": False, "verify_exp": True},
            )

    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)
        )

    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})

    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"])

        with pytest.deprecated_call():
            # The user explicitly sets verify=True,
            # but contradicts it in verify_signature.
            jwt.decode(
                jwt_message, secret, verify=True, options={"verify_signature": False}
            )

    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"])
        assert options == orig_options

    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")
        assert len(record) == 1
        assert "foo" in str(record[0].message)

    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")
        assert len(record) == 1
        assert "foo" in str(record[0].message)

    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"],
        )

        # Fails with `strict_aud`.
        with pytest.raises(InvalidAudienceError, match=r"Invalid audience \(strict\)"):
            jwt.decode(
                jwt_message,
                secret,
                audience=["urn:foo", "urn:bar"],
                options={"strict_aud": True},
                algorithms=["HS256"],
            )

    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",
[The command completed with exit code 0.]
[Current working directory: /workspace/pyjwt]
[Python interpreter: /usr/bin/python]
[Command finished with exit code 0]