Projects

pydantic-jwt

JWT tokens declared and validated as typed Pydantic models — signing, parsing and claim checks come for free from the model definition.

What it is#

A library for handling JWTs the way you'd want to in a typed codebase: declare your token as a Pydantic model, and get parsing, claim validation, signature verification and encoding out of it for free. Claims are typed, autocompleted and checked like any other Pydantic field — no more untyped dicts of claims floating around an auth layer.

Why I built it#

Most JWT handling in Python means juggling raw dicts, manual jwt.encode / jwt.decode calls, and ad-hoc validation of expiry, issuer and audience scattered across the codebase. None of that fits naturally with a Pydantic-based API like FastAPI, where everything else is a typed model. The idea was to make the token itself the model — one class that both issues and reads tokens, with claim rules (Exp, Nbf, Iat, IssClaim, AudClaim, custom claims) expressed as annotated types instead of hand-rolled checks, and with a verified_only mode that makes it structurally impossible to accept an unverified payload where a real token was required.

How it works#

pip install pydantic-jwt
from pydantic_jwt import ConfigDict, Exp, JWTModel, after, uuid

SECRET = "keep-me-out-of-your-source"

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

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

token = AccessToken(sub="user-42")
raw = token.generate()          # issue
token = AccessToken.from_token(raw)  # read + verify

Under the hood, JWTModel sits on top of Pydantic v2 and PyJWT: signing/keys and algorithm come from model_config, claim markers (Exp, Nbf, IssClaim, AudClaim, ...) are Annotated types that validate themselves against the current time or expected values, and any failure surfaces as a normal ValidationError with typed error codes (jwt_invalid_signature, jwt_claim_invalid, jwt_format, jwt_unverified_payload, etc.) so it slots into whatever error handling a Pydantic-based app already has. It integrates cleanly with FastAPI via Depends, reporting itself to OpenAPI as a JWT-typed string.

Status#

Published on PyPI, MIT-licensed, with CI (tests, ruff, mypy) and a MkDocs documentation site (built as a Cloudflare Worker) covering models, configuration, claims, defaults, validation, FastAPI integration, and security notes. Core functionality — issuing, verified reading, claim markers, key rotation, verified_only — is stable and documented; ongoing work is mostly docs polish and expanding the custom-claims guide.