FastAPI JWT Authentication
JWT auth with refresh tokens, password hashing, and role-based access.
# CLAUDE.md — FastAPI JWT Authentication
## Tokens
- Two tokens: **access token** (short-lived, ~15 min) + **refresh token** (longer, ~7–30 days).
- Sign with HS256 if you have one service. Switch to RS256 if you have multiple services that need to verify without sharing the signing key.
- Library: **python-jose** or **PyJWT**. Either is fine — pick one.
- Claims: `sub` (user id), `iat`, `exp`, `jti` (for revocation), and a `type` claim distinguishing access vs refresh.
## Storage on the client
- Web: HTTP-only, Secure, SameSite=Lax cookies for the refresh token. Access token in memory only.
- Mobile / native: secure storage (Keychain, EncryptedSharedPreferences). Never plain disk.
- Don't put tokens in `localStorage`. XSS reads it.
## Password hashing
- **bcrypt** via `passlib` or **argon2** via `argon2-cffi`. Argon2 if starting fresh.
- Cost factor: bcrypt 12+, argon2 default. Re-evaluate yearly.
- Never log passwords. Filter them out of error reporting.
## FastAPI integration
- Auth dependency:
```python
async def get_current_user(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)) -> User:
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
except JWTError:
raise HTTPException(401, "Invalid token")
user = await db.get(User, payload["sub"])
if not user:
raise HTTPException(401, "Invalid token")
return user
```
- For optional auth, write a separate dependency that returns `Optional[User]`. Don't make `get_current_user` accept `None`.
## Refresh flow
- Refresh endpoint: takes the refresh token, issues new access + refresh, **rotates** the refresh token.
- Track refresh tokens in a `refresh_tokens` table with `jti`, `user_id`, `expires_at`, `revoked_at`. Lookup on each refresh.
- On suspicious activity (token reuse), revoke the entire user session family.
## Roles & permissions
- Role on the user record, claim copied to the access token. Don't re-fetch the user just for role checks.
- Per-endpoint authorization: factory dependencies like `require_role("admin")`.
- For object-level permissions, check ownership inside the service — JWT only proves identity, not authorization.
## Logout
- Stateless JWTs can't truly be invalidated. For real logout:
- Delete the refresh-token row (the user can't refresh again)
- Optionally maintain a revocation list keyed by `jti` for unexpired access tokens
- Or shorten access-token TTL so revocation is "soon enough"
## Don't
- Don't use long-lived access tokens to avoid refresh logic. The refresh flow exists for revocation.
- Don't put sensitive data (email, role, plan) you'd hate to leak in the JWT payload — anyone can read it.
- Don't trust `iss` and `aud` without configuring them. Set them in encode and verify in decode.
- Don't roll your own crypto. Use library-supported algorithms only.
Other Python templates
Modern Python Rules
Type hints, ruff, black, uv, and pytest — opinionated Python defaults.
Python Data Science (pandas + numpy)
Notebook discipline, vectorization, and reproducible analysis with pandas.
Python Clean Architecture
Layered architecture with use-cases, repositories, and dependency inversion.
Python asyncio Patterns
asyncio fundamentals: tasks, gather, cancellation, and structured concurrency.
Python CLI Tools (Typer)
Build polished CLIs with Typer, Rich output, and clean argument parsing.