JWT Auth in FastAPI: Our Battle-Tested Implementation
A production-ready JWT authentication setup for FastAPI — access and refresh tokens, rotation, revocation, and the security details most tutorials skip.
Most JWT tutorials stop at “here is how to sign a token.” Production needs more: short-lived access tokens, refresh tokens, rotation, and a revocation story. Here is the FastAPI auth setup we actually ship, and the reasoning behind each decision.
Two tokens, two jobs
Use a short-lived access token (minutes) for API requests and a long-lived refresh token (days) to mint new access tokens. If an access token leaks, it expires fast; the refresh token lives in an HttpOnly cookie where JavaScript cannot read it.
from datetime import datetime, timedelta, timezone
import jwt # PyJWT
def make_token(sub: str, minutes: int, secret: str) -> str:
now = datetime.now(timezone.utc)
payload = {"sub": sub, "iat": now, "exp": now + timedelta(minutes=minutes)}
return jwt.encode(payload, secret, algorithm="HS256")Refresh token rotation
Every time a refresh token is used, issue a new one and invalidate the old. If an attacker replays a stolen-but-already-rotated token, you detect reuse and can revoke the entire session family. This is the single highest-leverage upgrade over naive JWT auth.
The revocation problem
JWTs are stateless, which is great for scale and awkward for logout. The pragmatic answer is a small denylist (or a per-user token version) in Redis: check it on refresh, keep access tokens short so the window of risk is tiny. We wire Redis in via our standard Docker Compose template.
Storage: cookies vs localStorage
- Refresh token →
HttpOnly; Secure; SameSite=Strictcookie. Inaccessible to JS, which neutralizes XSS token theft. - Access token → memory on the client. Never
localStorage, which is readable by any injected script.
Hardening checklist
- Always set
exp; validate it on every request. - Pin the algorithm — reject
alg: noneoutright. - Rotate signing secrets and support a key id (
kid). - Rate-limit the login and refresh endpoints.
For the broader threat model, the OWASP Top 10 is the reference we hold our auth code against.
Key takeaways
- Short access tokens + long refresh tokens, in an HttpOnly cookie.
- Rotate refresh tokens and detect reuse.
- Use a Redis denylist for real logout/revocation.
- Pin the algorithm and follow OWASP.
Authentication is too important to improvise. Our backend engineering team ships this setup as a baseline — talk to us about hardening your API. See also our RBAC guide for what comes after authentication.