Skip to content

JWTModel

JWTModel

Bases: BaseModel

A Pydantic model that is also a JWT.

Declare the claims as fields and set the keys in model_config; the model then both issues tokens (generate(), str()) and validates incoming ones (from_token(), or by validating a token string into the field).

Reading a token string verifies its signature; building a model from a dict does not, so never treat a model built from request data as authenticated.

Unknown claims are rejected by default (extra='forbid'); set extra='ignore' for tokens from third-party issuers.

Examples:

from pydantic_jwt import ConfigDict, Exp, JWTModel, after


class AccessToken(JWTModel):
    model_config = ConfigDict(algorithm='HS256', encoding_key=SECRET, decoding_key=SECRET)

    sub: str
    exp: Exp = after(minutes=15)


raw = str(AccessToken(sub='user-42'))
token = AccessToken.from_token(raw)
print(token.sub)
#> 'user-42'

jwt_str property

jwt_str: JWTStr

Return the signed token as a JWTStr.

Signs on every access, exactly like str().

from_token classmethod

from_token(
    jwt_str: str,
    *,
    decoding_key: str | None = None,
    algorithm: str | None = None,
    require_keys: bool | None = None,
    context: Any | None = None,
) -> T

Parse a token string, validate its claims and verify its signature.

Runs in three steps: structural parse, payload validation, then signature verification. A token that fails claim validation never reaches the signature check.

Parameters:

Name Type Description Default
jwt_str str

The compact token to read.

required
decoding_key str | None

Key to verify with, overriding model_config.

None
algorithm str | None

Algorithm to verify with, overriding model_config. It is never taken from the token's own alg header, which is what prevents algorithm-confusion attacks.

None
require_keys bool | None

Overrides model_config for this call.

None
context Any | None

Validation context forwarded to model_validate(). Pass {'validate_claims': False} to skip the claim markers.

None

Returns:

Type Description
T

The validated model.

Raises:

Type Description
ValidationError

The payload is malformed or a claim was rejected.

PydanticCustomError

The signature does not verify (jwt_invalid_signature), or no key is available and require_keys is on (jwt_missing_key).

Source code in pydantic_jwt/base.py
@classmethod
def from_token(
    cls: type[T],
    jwt_str: str,
    *,
    decoding_key: str | None = None,
    algorithm: str | None = None,
    require_keys: bool | None = None,
    context: Any | None = None,
) -> T:
    """Parse a token string, validate its claims and verify its signature.

    Runs in three steps: structural parse, payload validation, then signature
    verification. A token that fails claim validation never reaches the
    signature check.

    Args:
        jwt_str: The compact token to read.
        decoding_key: Key to verify with, overriding `model_config`.
        algorithm: Algorithm to verify with, overriding `model_config`. It is
            never taken from the token's own `alg` header, which is what
            prevents algorithm-confusion attacks.
        require_keys: Overrides `model_config` for this call.
        context: Validation context forwarded to `model_validate()`. Pass
            `{'validate_claims': False}` to skip the claim markers.

    Returns:
        The validated model.

    Raises:
        ValidationError: The payload is malformed or a claim was rejected.
        PydanticCustomError: The signature does not verify
            (`jwt_invalid_signature`), or no key is available and
            `require_keys` is on (`jwt_missing_key`).
    """

    jwt_obj = JWTStr(jwt_str)
    instance = cls.model_validate(jwt_obj.payload, context=context)
    instance._verify_signature(jwt_str, decoding_key, algorithm, require_keys)
    return instance

generate

generate(
    *,
    encoding_key: str | None = None,
    algorithm: str | None = None,
) -> str

Encode the model as a signed token.

The payload is model_dump(mode='json'), so custom field serialisers decide what lands in the token.

Parameters:

Name Type Description Default
encoding_key str | None

Key to sign with, overriding model_config. Useful during key rotation.

None
algorithm str | None

Algorithm to sign with, overriding model_config.

None

Returns:

Type Description
str

The compact, signed token.

Raises:

Type Description
PydanticCustomError

No key or algorithm is available (jwt_missing_key).

Source code in pydantic_jwt/base.py
def generate(self, *, encoding_key: str | None = None, algorithm: str | None = None) -> str:
    """Encode the model as a signed token.

    The payload is `model_dump(mode='json')`, so custom field serialisers
    decide what lands in the token.

    Args:
        encoding_key: Key to sign with, overriding `model_config`. Useful
            during key rotation.
        algorithm: Algorithm to sign with, overriding `model_config`.

    Returns:
        The compact, signed token.

    Raises:
        PydanticCustomError: No key or algorithm is available
            (`jwt_missing_key`).
    """

    if encoding_key is None:
        encoding_key = self.model_config.get("encoding_key")
    if algorithm is None:
        algorithm = self.model_config.get("algorithm")

    if encoding_key is None or algorithm is None:
        raise PydanticCustomError(
            "jwt_missing_key",
            "encoding_key and algorithm must be set in model_config to generate a token",
            {"model": self.__class__.__name__, "keys": "encoding_key, algorithm"},
        )

    payload = self.model_dump(mode="json")
    return jwt.encode(payload, encoding_key, algorithm=algorithm)