Skip to content
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ Fixed
- Validate key against allowed types for Algorithm family in `#964 <https://github.com/jpadilla/pyjwt/pull/964>`__
- Add iterator for JWKSet in `#1041 <https://github.com/jpadilla/pyjwt/pull/1041>`__
- Validate `iss` claim is a string during encoding and decoding by @pachewise in `#1040 <https://github.com/jpadilla/pyjwt/pull/1040>`__
- Improve typing/logic for `options` in decode, decode_complete by @pachewise in `#1045 <https://github.com/jpadilla/pyjwt/pull/1045>`__

Added
~~~~~

- Docs: Add example of using leeway with nbf by @djw8605 in `#1034 <https://github.com/jpadilla/pyjwt/pull/1034>`__
- Docs: Refactored docs with ``autodoc``; added ``PyJWS`` and ``jwt.algorithms`` docs by @pachewise in `#1045 <https://github.com/jpadilla/pyjwt/pull/1045>`__

`v2.10.1 <https://github.com/jpadilla/pyjwt/compare/2.10.0...2.10.1>`__
-----------------------------------------------------------------------
Expand Down
239 changes: 33 additions & 206 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,230 +3,57 @@ API Reference

.. module:: jwt

.. function:: encode(payload, key, algorithm="HS256", headers=None, json_encoder=None)
.. autofunction:: encode(payload, key, algorithm="HS256", headers=None, json_encoder=None) -> str

Encode the ``payload`` as JSON Web Token.
.. autofunction:: decode(jwt, key="", algorithms=None, options=None, audience=None, issuer=None, leeway=0) -> dict[str, typing.Any]

:param dict payload: JWT claims, e.g. ``dict(iss=..., aud=..., sub=...)``
:param key: a key suitable for the chosen algorithm:
.. autoclass:: PyJWK
:class-doc-from: init
:members:

* for **asymmetric algorithms**: PEM-formatted private key, a multiline string
* for **symmetric algorithms**: plain string, sufficiently long for security
.. property:: algorithm_name

:type key: str or bytes or jwt.PyJWK
:param str algorithm: algorithm to sign the token with, e.g. ``"ES256"``.
If ``headers`` includes ``alg``, it will be preferred to this parameter.
If ``key`` is a :class:`jwt.PyJWK` object, by default the key algorithm will be used.
:param dict headers: additional JWT header fields, e.g. ``dict(kid="my-key-id")``.
:param json.JSONEncoder json_encoder: custom JSON encoder for ``payload`` and ``headers``
:rtype: str
:returns: a JSON Web Token
:type: str

.. function:: decode(jwt, key="", algorithms=None, options=None, audience=None, issuer=None, leeway=0)
The name of the algorithm used by the key.

Verify the ``jwt`` token signature and return the token claims.
.. property:: Algorithm

:param str jwt: the token to be decoded
:param key: the key suitable for the allowed algorithm
:type key: str or bytes or jwt.PyJWK

:param list algorithms: allowed algorithms, e.g. ``["ES256"]``
If ``key`` is a :class:`jwt.PyJWK` object, allowed algorithms will default to the key algorithm.

.. warning::

Do **not** compute the ``algorithms`` parameter based on
the ``alg`` from the token itself, or on any other data
that an attacker may be able to influence, as that might
expose you to various vulnerabilities (see `RFC 8725 §2.1
<https://www.rfc-editor.org/rfc/rfc8725.html#section-2.1>`_). Instead,
either hard-code a fixed value for ``algorithms``, or
configure it in the same place you configure the
``key``. Make sure not to mix symmetric and asymmetric
algorithms that interpret the ``key`` in different ways
(e.g. HS\* and RS\*).

:param dict options: extended decoding and validation options

* ``verify_signature=True`` verify the JWT cryptographic signature
* ``require=[]`` list of claims that must be present.
Example: ``require=["exp", "iat", "nbf"]``.
**Only verifies that the claims exists**. Does not verify that the claims are valid.
* ``verify_aud=verify_signature`` check that ``aud`` (audience) claim matches ``audience``
* ``verify_iss=verify_signature`` check that ``iss`` (issuer) claim matches ``issuer``
* ``verify_exp=verify_signature`` check that ``exp`` (expiration) claim value is in the future
* ``verify_iat=verify_signature`` check that ``iat`` (issued at) claim value is an integer
* ``verify_nbf=verify_signature`` check that ``nbf`` (not before) claim value is in the past
* ``strict_aud=False`` check that the ``aud`` claim is a single value (not a list), and matches ``audience`` exactly

.. warning::

``exp``, ``iat`` and ``nbf`` will only be verified if present.
Please pass respective value to ``require`` if you want to make
sure that they are always present (and therefore always verified
if ``verify_exp``, ``verify_iat``, and ``verify_nbf`` respectively
is set to ``True``).

:param audience: optional, the value for ``verify_aud`` check
:type audience: Union[str, Iterable]
:param str issuer: optional, the value for ``verify_iss`` check
:param float leeway: a time margin in seconds for the expiration check
:rtype: dict
:returns: the JWT claims

.. class:: PyJWK

A class that represents a `JSON Web Key <https://www.rfc-editor.org/rfc/rfc7517>`_.

.. method:: __init__(self, jwk_data, algorithm=None)

:param dict data: The decoded JWK data.
:param algorithm: The key algorithm. If not specific, the key's ``alg`` will be used.
:type algorithm: str or None

.. staticmethod:: from_json(data, algorithm=None)

:param str data: The JWK data, as a JSON string.
:param algorithm: The key algorithm. If not specific, the key's ``alg`` will be used.
:type algorithm: str or None

:returntype: jwt.PyJWK

Create a :class:`jwt.PyJWK` object from a JSON string.

.. property:: algorithm_name

:type: str

The name of the algorithm used by the key.

.. property:: Algorithm

The ``Algorithm`` class associated with the key.

.. property:: key_type

:type: str or None

The ``kty`` property from the JWK.

.. property:: key_id

:type: str or None

The ``kid`` property from the JWK.

.. property:: public_key_use

:type: str or None

The ``use`` property from the JWK.
The :py:class:`Algorithm` class associated with the key.

.. module:: jwt.api_jwt

.. function:: decode_complete(jwt, key="", algorithms=None, options=None, audience=None, issuer=None, leeway=0)
.. autofunction:: decode_complete(jwt, key="", algorithms=None, options=None, audience=None, issuer=None, leeway=0) -> dict[str, typing.Any]

Identical to ``jwt.decode`` except for return value which is a dictionary containing the token header (JOSE Header),
the token payload (JWT Payload), and token signature (JWT Signature) on the keys "header", "payload",
and "signature" respectively.
.. note:: TODO: Finish documenting PyJWS class
.. module:: jwt.api_jws

:param str jwt: the token to be decoded
:param str key: the key suitable for the allowed algorithm
.. autoclass:: jwt.api_jws.PyJWS
:members:

:param list algorithms: allowed algorithms, e.g. ``["ES256"]``

.. warning::

Do **not** compute the ``algorithms`` parameter based on
the ``alg`` from the token itself, or on any other data
that an attacker may be able to influence, as that might
expose you to various vulnerabilities (see `RFC 8725 §2.1
<https://www.rfc-editor.org/rfc/rfc8725.html#section-2.1>`_). Instead,
either hard-code a fixed value for ``algorithms``, or
configure it in the same place you configure the
``key``. Make sure not to mix symmetric and asymmetric
algorithms that interpret the ``key`` in different ways
(e.g. HS\* and RS\*).

:param dict options: extended decoding and validation options

* ``verify_signature=True`` verify the JWT cryptographic signature
* ``require=[]`` list of claims that must be present.
Example: ``require=["exp", "iat", "nbf"]``.
**Only verifies that the claims exists**. Does not verify that the claims are valid.
* ``verify_aud=verify_signature`` check that ``aud`` (audience) claim matches ``audience``
* ``verify_iss=verify_signature`` check that ``iss`` (issuer) claim matches ``issuer``
* ``verify_exp=verify_signature`` check that ``exp`` (expiration) claim value is in the future
* ``verify_iat=verify_signature`` check that ``iat`` (issued at) claim value is an integer
* ``verify_nbf=verify_signature`` check that ``nbf`` (not before) claim value is in the past
* ``strict_aud=False`` check that the ``aud`` claim is a single value (not a list), and matches ``audience`` exactly
Algorithms
----------

.. warning::
.. automodule:: jwt.algorithms
:members: Algorithm, AllowedPrivateKeys, AllowedPublicKeys

``exp``, ``iat`` and ``nbf`` will only be verified if present.
Please pass respective value to ``require`` if you want to make
sure that they are always present (and therefore always verified
if ``verify_exp``, ``verify_iat``, and ``verify_nbf`` respectively
is set to ``True``).

:param Iterable audience: optional, the value for ``verify_aud`` check
:param str issuer: optional, the value for ``verify_iss`` check
:param float leeway: a time margin in seconds for the expiration check
:rtype: dict
:returns: Decoded JWT with the JOSE Header on the key ``header``, the JWS
Payload on the key ``payload``, and the JWS Signature on the key ``signature``.
Types
----------

.. note:: TODO: Document PyJWS class
.. module:: jwt.types
:synopsis: Type validation used in the JWT API
.. autoclass:: jwt.types.SigOptions
:members:
:undoc-members:
.. autoclass:: jwt.types.Options
:members:
:undoc-members:

Exceptions
----------

.. currentmodule:: jwt.exceptions


.. class:: InvalidTokenError

Base exception when ``decode()`` fails on a token

.. class:: DecodeError

Raised when a token cannot be decoded because it failed validation

.. class:: InvalidSignatureError

Raised when a token's signature doesn't match the one provided as part of
the token.

.. class:: ExpiredSignatureError

Raised when a token's ``exp`` claim indicates that it has expired

.. class:: InvalidAudienceError

Raised when a token's ``aud`` claim does not match one of the expected
audience values

.. class:: InvalidIssuerError

Raised when a token's ``iss`` claim does not match the expected issuer

.. class:: InvalidIssuedAtError

Raised when a token's ``iat`` claim is non-numeric

.. class:: ImmatureSignatureError

Raised when a token's ``nbf`` or ``iat`` claims represent a time in the future

.. class:: InvalidKeyError

Raised when the specified key is not in the proper format

.. class:: InvalidAlgorithmError

Raised when the specified algorithm is not recognized by PyJWT

.. class:: MissingRequiredClaimError

Raised when a claim that is required to be present is not contained
in the claimset
.. automodule:: jwt.exceptions
:members:
:inherited-members:
:show-inheritance:
4 changes: 4 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,12 @@ def find_version(*file_paths) -> str:
# Intersphinx extension.
intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
"cryptography": ("https://cryptography.io/en/latest/", None),
}

# Hack for allowing aliases within TYPE_CHECKING to be documented
os.environ["SPHINX_BUILD"] = "1"

# -- Options for HTML output ----------------------------------------------


Expand Down
64 changes: 34 additions & 30 deletions jwt/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import hashlib
import hmac
import json
import os
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, ClassVar, Literal, NoReturn, cast, overload

Expand Down Expand Up @@ -91,32 +92,36 @@
Ed448PublicKey,
)

has_crypto = True
except ModuleNotFoundError:
has_crypto = False

if TYPE_CHECKING or bool(os.getenv("SPHINX_BUILD", "")):
from typing import TypeAlias

if TYPE_CHECKING:
from typing import TypeAlias
from cryptography.hazmat.primitives.asymmetric.types import (
PrivateKeyTypes,
PublicKeyTypes,
)

from cryptography.hazmat.primitives.asymmetric.types import (
PrivateKeyTypes,
PublicKeyTypes,
)
# Type aliases for convenience in algorithms method signatures
AllowedRSAKeys: TypeAlias = RSAPrivateKey | RSAPublicKey
AllowedECKeys: TypeAlias = EllipticCurvePrivateKey | EllipticCurvePublicKey
AllowedOKPKeys: TypeAlias = (
Ed25519PrivateKey | Ed25519PublicKey | Ed448PrivateKey | Ed448PublicKey
)
AllowedKeys: TypeAlias = AllowedRSAKeys | AllowedECKeys | AllowedOKPKeys
#: Type alias for allowed ``cryptography`` private keys (requires ``cryptography`` to be installed)
AllowedPrivateKeys: TypeAlias = (
RSAPrivateKey
| EllipticCurvePrivateKey
| Ed25519PrivateKey
| Ed448PrivateKey
)
#: Type alias for allowed ``cryptography`` public keys (requires ``cryptography`` to be installed)
AllowedPublicKeys: TypeAlias = (
RSAPublicKey | EllipticCurvePublicKey | Ed25519PublicKey | Ed448PublicKey
)

# Type aliases for convenience in algorithms method signatures
AllowedRSAKeys: TypeAlias = RSAPrivateKey | RSAPublicKey
AllowedECKeys: TypeAlias = EllipticCurvePrivateKey | EllipticCurvePublicKey
AllowedOKPKeys: TypeAlias = (
Ed25519PrivateKey | Ed25519PublicKey | Ed448PrivateKey | Ed448PublicKey
)
AllowedKeys: TypeAlias = AllowedRSAKeys | AllowedECKeys | AllowedOKPKeys
AllowedPrivateKeys: TypeAlias = (
RSAPrivateKey | EllipticCurvePrivateKey | Ed25519PrivateKey | Ed448PrivateKey
)
AllowedPublicKeys: TypeAlias = (
RSAPublicKey | EllipticCurvePublicKey | Ed25519PublicKey | Ed448PublicKey
)
has_crypto = True
except ModuleNotFoundError:
has_crypto = False


requires_cryptography = {
Expand All @@ -139,7 +144,7 @@ def get_default_algorithms() -> dict[str, Algorithm]:
"""
Returns the algorithms that are implemented by the library.
"""
default_algorithms = {
default_algorithms: dict[str, Algorithm] = {
"none": NoneAlgorithm(),
"HS256": HMACAlgorithm(HMACAlgorithm.SHA256),
"HS384": HMACAlgorithm(HMACAlgorithm.SHA384),
Expand Down Expand Up @@ -202,13 +207,12 @@ def compute_hash_digest(self, bytestr: bytes) -> bytes:
def check_crypto_key_type(self, key: PublicKeyTypes | PrivateKeyTypes):
"""Check that the key belongs to the right cryptographic family.

Note that this method only works when `cryptography` is installed.
Note that this method only works when ``cryptography`` is installed.

Args:
key (Any): Potentially a cryptography key
Raises:
ValueError: if `cryptography` is not installed, or this method is called by a non-cryptography algorithm
InvalidKeyError: if the key doesn't match the expected key classes
:param key: Potentially a cryptography key
:type key: :py:data:`PublicKeyTypes <cryptography.hazmat.primitives.asymmetric.types.PublicKeyTypes>` | :py:data:`PrivateKeyTypes <cryptography.hazmat.primitives.asymmetric.types.PrivateKeyTypes>`
:raises ValueError: if ``cryptography`` is not installed, or this method is called by a non-cryptography algorithm
:raises InvalidKeyError: if the key doesn't match the expected key classes
"""
if not has_crypto or self._crypto_key_types is None:
raise ValueError(
Expand Down
Loading