Quickstart¶
1. Declare the token¶
A token model is a Pydantic model. Its fields are the claims, and
model_config holds the keys used to sign and verify it.
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()
Three things are happening in the field list:
sub: stris an ordinary required field — nothing JWT-specific about it.exp: Expis anintthat refuses to validate once the timestamp is in the past, andafter(minutes=15)gives it a default computed fresh for every instance.uuid()gives each issued token a uniquejti.
2. Issue a token¶
Construct the model and turn it into a string:
token = AccessToken(sub="user-42")
raw = str(token)
#> 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQyIiwi...'
str(token) calls generate(), which dumps
the model to JSON and signs it with encoding_key and algorithm. The
jwt_str property returns the same string as a
JWTStr, which can be taken apart:
token.jwt_str.header #> {'alg': 'HS256', 'typ': 'JWT'}
token.jwt_str.payload #> {'sub': 'user-42', 'exp': 1788009360, 'jti': '9044...'}
3. Read one back¶
from_token() parses the string, validates
every claim and verifies the signature against decoding_key:
AccessToken.model_validate(raw) does the same thing and is what runs when a
token string arrives in a field of some other model.
4. Handle the failures¶
Everything that can go wrong has a distinct error type:
from pydantic import ValidationError
try:
AccessToken.model_validate(untrusted)
except ValidationError as exc:
print(exc.errors()[0]["type"])
#> 'jwt_invalid_signature' — signed with the wrong key
#> 'jwt_claim_invalid' — expired, wrong issuer, ...
#> 'jwt_format' — not three dot-separated base64url segments
#> 'extra_forbidden' — a claim the model does not declare
See Validation and errors for the full list and for the
one case that raises PydanticCustomError instead of ValidationError.
5. Wire it into a framework¶
The whole point is that a verified token arrives as a typed object:
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from pydantic import ValidationError
app = FastAPI()
bearer = HTTPBearer()
def current_token(
credentials: Annotated[HTTPAuthorizationCredentials, Depends(bearer)],
) -> AccessToken:
try:
return AccessToken.from_token(credentials.credentials)
except (ValidationError, ValueError):
raise HTTPException(status_code=401, detail="Invalid token") from None
@app.get("/me")
def me(token: Annotated[AccessToken, Depends(current_token)]) -> dict[str, str]:
return {"user": token.sub}
A complete application — login, refresh, scopes, error handling and OpenAPI — is in the FastAPI guide.
What to read next¶
- Token models — the full
JWTModelsurface. - Claims —
Exp,Nbf,Iat,iss,aud, custom claims. - Security notes — the two mistakes worth knowing about before this goes near production.