Authentication and JWT
Introduction
API clients authenticate with JWT access tokens sent in the Authorization: Bearer header. FastAPI documents the OAuth2 password flow pattern: login with username/password, receive a token, pass token to protected routes. This chapter implements user registration, login, password hashing, and Depends(get_current_user).
Prerequisites
- SQLAlchemy With FastAPI
- Settings and Configuration
pip install "python-jose[cryptography]" passlib[bcrypt] python-multipart
Security Module
app/core/security.py:
Code explanation:
sub(subject) stores user id as string in JWTsecret_keyfrom settings signs tokens—must be strong in production
Compare Spring Boot JWT and Flask JWT.
User Model and Schemas
app/models/user.py:
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import String
from app.db.session import Base
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(String(80), unique=True, index=True)
hashed_password: Mapped[str] = mapped_column(String(255))app/schemas/user.py:
OAuth2 Bearer Dependency
app/api/deps.py:
Code explanation:
tokenUrlmust match login route path for Swagger Authorize buttonOAuth2PasswordBearerextracts Bearer token from header
Auth Routes
app/api/v1/endpoints/auth.py:
Add import:
from fastapi.security import OAuth2PasswordRequestFormCode explanation:
- Login uses form fields
usernameandpassword(OAuth2 spec)—not JSON body - Swagger Authorize sends same format
JSON login alternative (optional second endpoint):
class LoginJSON(BaseModel):
username: str
password: str
@router.post("/login/json", response_model=Token)
def login_json(body: LoginJSON, db: Session = Depends(get_db)):
...Protected Route
from app.api.deps import get_current_user
@router.get("/me", response_model=UserRead)
def read_me(current_user: User = Depends(get_current_user)):
return current_userRegister router in api.py.
Test With curl
Register:
curl -X POST http://127.0.0.1:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"alice","password":"secret123"}'Login (form):
TOKEN=$(curl -s -X POST http://127.0.0.1:8000/api/v1/auth/login \
-d "username=alice&password=secret123" | jq -r .access_token)Protected:
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8000/api/v1/auth/meSwagger Authorize
Open /docs → Authorize → enter username/password (OAuth2 flow) or paste Bearer <token> depending on UI mode.
Warning
Never Return hashed_password
Use UserRead without password field in all responses.
Refresh Tokens (Concept)
Short access token + long refresh token improves security. Store refresh tokens server-side or in HTTP-only cookies—full implementation is optional stretch goal.
FAQ
401 on protected route?
Missing header, expired token, or wrong SECRET_KEY after restart.
Login expects form not JSON?
Use OAuth2PasswordRequestForm or add JSON login endpoint.
bcrypt version warnings?
Pin passlib and bcrypt compatible versions in requirements.txt.
PyJWT instead of python-jose?
Both work—adjust encode/decode API accordingly.
Token in query string?
Avoid—logs and referrers leak tokens.
HS256 vs RS256?
HS256 + shared secret is fine for monolith; RS256 for multi-service verification.