FastAPI + PyJWT bearer auth: working versions (tested 2026-10-15)
OAuth2 password flow in FastAPI issuing PyJWT tokens, with a current-user dependency guarding routes.
passed2 passed · 0 failed2026-10-15 11:53 UTC- Status
- passed
- Last passed
- 2026-10-15 11:53 UTC
- Tests
- 2 / 2
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38730348195
The code
Exactly the files that passed. Your agent can pull them with get_recipe("fastapi-jwt-auth") (install), or see them on GitHub.
fastapi==0.143.1 pyjwt==2.15.1 httpx==0.28.1 python-multipart==0.0.32 pytest==9.1.1
import os
from datetime import datetime, timedelta, timezone
import jwt
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
SECRET = os.environ.get("JWT_SECRET", "dev-only-secret-change-me-32-bytes!!")
ALGO = "HS256"
oauth2 = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
def make_token(sub: str, minutes: int = 15) -> str:
exp = datetime.now(timezone.utc) + timedelta(minutes=minutes)
return jwt.encode({"sub": sub, "exp": exp}, SECRET, algorithm=ALGO)
def current_user(token: str = Depends(oauth2)) -> str:
try:
return jwt.decode(token, SECRET, algorithms=[ALGO])["sub"]
except jwt.PyJWTError:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "invalid token", headers={"WWW-Authenticate": "Bearer"})
@app.post("/token")
def login(form: OAuth2PasswordRequestForm = Depends()):
if form.password != "correct-horse":
raise HTTPException(400, "bad credentials")
return {"access_token": make_token(form.username), "token_type": "bearer"}
@app.get("/me")
def me(user: str = Depends(current_user)):
return {"user": user}
from fastapi.testclient import TestClient
from app.main import app, make_token
client = TestClient(app)
def test_login_and_me():
tok = client.post("/token", data={"username": "ada", "password": "correct-horse"}).json()["access_token"]
assert client.get("/me", headers={"Authorization": f"Bearer {tok}"}).json() == {"user": "ada"}
def test_rejects_bad_and_expired():
assert client.get("/me").status_code == 401
expired = make_token("ada", minutes=-1)
assert client.get("/me", headers={"Authorization": f"Bearer {expired}"}).status_code == 401
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
FastAPI OAuth2 password flow with PyJWT tokens and scope checks
OAuth2 password login issuing PyJWT access tokens, Argon2 hashes via pwdlib, and per-route scope checks with SecurityScopes, tested offline with TestClient.
FastAPI + Pydantic v2 validators, discriminated unions and serializers
Pydantic v2 patterns in FastAPI that agents often get wrong: field_validator, model_validator, discriminated unions, computed_field and response_model filtering.
FastAPI + SQLAlchemy 2 CRUD with dependency-injected sessions
FastAPI routes backed by SQLAlchemy 2.x typed models, a per-request session dependency, and TestClient tests on SQLite.
FastAPI + Stripe webhook signature verification
Verify Stripe webhook signatures in FastAPI from the raw request body and dispatch on event type. Tested offline with locally signed payloads.