OAuth2 & JWT in FastAPI
FastAPI integrates OAuth2 authentication natively through fastapi.security. Combined with JWT (JSON Web Tokens), you get a stateless, secure authentication flow perfect for modern APIs.
Install Dependencies
pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt] python-multipart
| Library | Purpose |
|---|---|
python-jose | JWT token creation & verification |
passlib + bcrypt | Password hashing |
python-multipart | Form data parsing for OAuth2 |
OAuth2PasswordBearer
OAuth2PasswordBearer tells FastAPI that the app expects a token in the Authorization: Bearer <token> header:
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
tokenUrlpoints to the login endpoint that issues tokens- The dependency returns the raw token string
- If the header is missing, FastAPI returns a 401 Unauthorized automatically
JWT Configuration
from datetime import datetime, timedelta, timezone
from jose import JWTError, jwt
SECRET_KEY = "your-secret-key-change-in-production"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
SECRET_KEYmust be a cryptographically random string (useopenssl rand -hex 32)ALGORITHMis typicallyHS256(HMAC with SHA-256)- Token expiry is set per environment (shorter for production)
Create Access Token
def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
expire = datetime.now(timezone.utc) + (
expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
- The payload should contain minimal claims:
sub(user identifier),exp(expiration) - Never store sensitive data (passwords, credit cards) in the token
- Always use timezone-aware UTC timestamps for
exp
Token Endpoint
The /token endpoint accepts username and password form data and returns an access token:
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel
router = APIRouter()
class Token(BaseModel):
access_token: str
token_type: str
@router.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = create_access_token(data={"sub": user.username})
return Token(access_token=access_token, token_type="bearer")
OAuth2PasswordRequestFormautomatically parsesusername/passwordform fields- Always return
token_type: "bearer"for OAuth2 compliance - The
WWW-Authenticateheader in error responses helps clients understand auth requirements
Dependency Injection for Token Verification
from jose import JWTError, jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user(fake_db, username=username)
if user is None:
raise credentials_exception
return user
Depends(oauth2_scheme)extracts the token from the requestjwt.decode()validates signature and expiry automatically- The dependency is reusable across all protected routes
Protecting Routes
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
Simply inject get_current_user into any endpoint that requires authentication. FastAPI handles the full flow - token extraction, validation, and error responses.
Token Expiry & Refresh
JWT tokens are stateless - you cannot revoke them before expiry. Common strategies:
- Short-lived access tokens (15–30 minutes) limit the damage of a leaked token
- Refresh tokens (long-lived, stored in DB) allow issuing new access tokens without re-login
- A
/refreshendpoint validates the refresh token and issues a fresh access token
@router.post("/refresh")
async def refresh_token(refresh_token: str, current_user: User = Depends(get_current_user)):
# Validate refresh token from DB
# Issue new access token
return Token(access_token=create_access_token(data={"sub": current_user.username}), token_type="bearer")
Key Takeaways
OAuth2PasswordBearerextracts Bearer tokens from theAuthorizationheaderpython-josehandles JWT creation (jwt.encode) and verification (jwt.decode)- Tokens must include an
expclaim and use a strongSECRET_KEY - The
/tokenendpoint returnsaccess_token+token_typein OAuth2 format get_current_useris a reusable dependency injected viaDepends()into protected routes- Token expiry is enforced at decode time - expired tokens are rejected automatically
- Use short-lived access tokens with refresh tokens for production deployments