Build1 distinct publisher2 min readPublished
pydantic-jwt gives a real type to the one payload most Python services still pass around as dict[str, Any]. The same constructor that signs tokens is why version 1.0.0 needed an opt-in flag to refuse unverified input.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The dual role is where it breaks. One class both mints and parses [5], so its constructor has to accept a plain claims dict; there is nothing to sign otherwise [13]. Pydantic cannot tell a dict your own code assembled from a dict that arrived over the wire, and FastAPI's job on a body parameter is precisely to build the annotated model out of untrusted JSON [14]. A signature check you can skip by not presenting a signature is an unusual kind of optional.
The switch that closes it is opt-in. Nothing refuses the dict path unless `model_config` names `verified_only=True`, so any model that omits the flag keeps the permissive constructor [19]. Version 0.2.0 documented the hazard and shipped it, and the author's own verdict is that documenting a hole is not fixing it [15]. Per-model opt-in is defensible here, because the same library has to serve the minting side, where a claims dict is the entire point [13].
The claim annotations are the part I would keep even if I skipped the package. `IssClaim` and `AudClaim` compare against a value you name [8], and they are ordinary `Annotated` metadata, so they compose with whatever else Pydantic does to that field, with `Claim` there to subclass for checks the library does not ship [9]. The author's framing of why `aud` gets skipped is the honest one: it is one more `if` [10]. In an annotation, omitting it means deleting a line that sits next to the field it protects.
Failure handling is the other thing that transfers. `ValidationError` and `PydanticCustomError` both subclass `ValueError`, so one `except` clause catches expired, forged and missing-claim tokens alike, and the error type still separates go-refresh from log-in-again [12].
Before putting this on an auth path I would want the token verified in more than one handler, so a single declaration pays for itself, and mypy running in CI, because without that a typed payload buys autocomplete rather than enforcement [17]. Accepting a library type on the boundary that decides caller identity is a heavier commitment than accepting one on a request body. The fix arrived the hard way: the author reports three attempts, and defining `__init__` on the model was among the two that failed [18].
Ranked by verification strength, evidence, and original report placement.
With a dict payload, payload["sub"] works and payload["user_id"] returns a KeyError at three in the morning, and nothing in the editor knows the difference.
pydantic-jwt lets a token be declared as a JWTModel subclass whose model_config = ConfigDict(algorithm="HS256", encoding_key=SECRET, decoding_key=SECRET), with fields such as sub: str, scopes: list[str] = [], exp: Exp = after(minutes=15) and jti: str = uuid().
One JWTModel class is both ends of the flow: AccessToken(sub="user-42", scopes=["read"]).generate() produces the raw token, and AccessToken.from_token(raw) returns an instance whose sub is a str the editor knows about.
from_token() parses the token, validates the claims and verifies the signature; if any of that fails you get an exception rather than a dict you have to remember to check.
Exp, Nbf and Iat are annotated int types that compare against the current clock, and IssClaim and AudClaim compare against a value you expect.
IssClaim("https://auth.example.com") and AudClaim("billing-api") are used as Annotated metadata on the iss and aud fields to compare the token's values against the ones you declare.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 30, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
PyCharm 2026.2 closes the SQLAlchemy false positives that got inspections switched off1 distinct publisher
build
Return the admission record, not the log line: one memory service's case for receipts1 distinct publisher
build
Four test runs, a week's API budget: the seam that gets the model out of CI1 distinct publisher
build
The model swap that cost three days: write the response contract before you pick a provider1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One author, but checkable code
Every factual load in this story comes from the same dev.to post, written by the person who wrote the library — yet it is the unusually testable kind of single source: the code samples either behave as described against the published package or they do not, and the sharpest item in the piece, that a model used as a request body yields a token nobody signed, is not something a promoter invents. What no one supplies is an outside pair of eyes confirming that verified_only actually seals the path it claims to seal.
A release and nothing behind it
What is observable amounts to a package on PyPI, a dependency floor of Python 3.10 and Pydantic 2.10, and a version number that moved from 0.2.0 to 1.0.0. No downloads, no dependent projects, no service running this in front of an auth boundary, no second maintainer. An announcement is the thinnest adoption signal there is, and that is what we have.
Undersold by its own author
The best paragraphs are the self-incriminating ones: a constructor that accepts attacker JSON unless you flip a flag, no revocation, no JWKS client, tokens signed but not encrypted, and a closing pointer to the security notes in preference to 'my marketing'. Vendors bury that material; this piece leads with it. Pushing back the other way is only the unmeasured opening flourish about every Python codebase the author has read, which is a hunch dressed as a survey.
Author, publisher and beneficiary are one person
The post signs off with 'I am the author' and ends in docs, GitHub, PyPI and an install line, so the person choosing which sharp edges to name is the person whose package gains from being adopted. That does not make the account unreliable — he volunteers the vulnerability 0.2.0 shipped with — but the vocabulary is his: calling the old permissive constructor something 1.0.0 gave an 'off switch' is a gentler framing than a default-unsafe input path deserves.
Mechanism clear, uptake unknown
How the library behaves is described precisely enough that a developer could confirm or demolish it in an afternoon. Whether it belongs in front of an authentication boundary rests on things this reporting never reaches: independent review of the verification path, evidence of real deployments, and how often the permissive default catches people who skipped the security notes.