Security and Authentication
Learn to implement robust security and authentication mechanisms to protect your FastAPI applications.
Content
OAuth2 and JWT
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
OAuth2 and JWT in FastAPI — The No-BS Guide (with a Wink)
If authentication were a nightclub, OAuth2 would be the bouncer with a clipboard and JWTs would be the wristbands. One tells you who gets in; the other says how long they can party.
Why this matters (and why you should care)
You already learned the power of Dependency Injection in FastAPI — how it makes your code composable and testable. Now we level up: securely identifying and authorizing users and services using OAuth2 and JSON Web Tokens (JWT). These are the bread-and-butter pieces for real-world APIs: protecting routes, issuing tokens, validating scopes, and integrating third-party auth.
This guide builds on dependency patterns (parameterized dependencies, SecurityScopes, etc.) so we won't re-teach DI basics — we will use them to enforce security like a pro.
Quick map: OAuth2 vs JWT (short and spicy)
- OAuth2: A framework (flows, roles, and grants). It tells you how to obtain tokens.
- JWT: A token format (signed JSON). It tells you what the token contains and how to verify it.
Table: OAuth2 flows at-a-glance
| Flow | Use case | Who holds credentials? | Recommended for |
|---|---|---|---|
| Authorization Code | Web apps / third-party integrations | User + client + auth server | Native & server-side web apps |
| Implicit | Old SPAs (deprecated) | Client only | Avoid — use auth code + PKCE |
| Resource Owner Password Credentials | First-party apps | User gives creds to client | Only trusted first-party apps |
| Client Credentials | Machine-to-machine | Client (no user) | Service-to-service |
JWT pros and cons
- Pros: stateless, compact, easy to verify, self-contained claims
- Cons: revocation is harder; large claims bloat headers; must be short-lived
How FastAPI plugs in (the cool part)
FastAPI gives you helpers: OAuth2PasswordBearer, OAuth2PasswordRequestForm, SecurityScopes, and seamless OpenAPI docs integration so the swagger UI gets an "Authorize" button. Combine that with DI, and you get clean reusable auth guards.
Below is the standard pattern you'll use.
Minimal working example (login -> JWT -> protected route)
from datetime import datetime, timedelta
from typing import Optional, List
from fastapi import FastAPI, Depends, HTTPException, status, Security
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm, SecurityScopes
from jose import JWTError, jwt
from passlib.context import CryptContext
# SECURITY CONFIG — keep these outside source control in prod
SECRET_KEY = "super-secret-key-change-me"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 15
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="/token",
scopes={"me": "Read personal info", "items": "Read items"}
)
# fake DB
fake_users_db = {
"alice": {"username": "alice", "hashed_password": pwd_context.hash("secret"), "scopes": ["me"]}
}
def verify_password(plain, hashed):
return pwd_context.verify(plain, hashed)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
@app.post('/token')
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_users_db.get(form_data.username)
if not user or not verify_password(form_data.password, user['hashed_password']):
raise HTTPException(status_code=400, detail='Incorrect username or password')
access_token = create_access_token({"sub": user['username'], "scopes": user.get('scopes', [])})
return {"access_token": access_token, "token_type": "bearer"}
# Dependency: validate token and scopes (uses DI + SecurityScopes)
async def get_current_user(security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)):
if security_scopes.scopes:
# FastAPI passes required scopes if endpoint used Security(...)
required = security_scopes.scopes
else:
required = []
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')
token_scopes = payload.get('scopes', [])
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = fake_users_db.get(username)
if not user:
raise credentials_exception
for scope in required:
if scope not in token_scopes:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
detail='Not enough permissions',
headers={"WWW-Authenticate": f'Bearer scope="{" ".join(required)}"'})
return user
@app.get('/users/me')
async def read_users_me(current_user: dict = Security(get_current_user, scopes=["me"])):
return {"username": current_user['username']}
Notes:
Security(get_current_user, scopes=["me"])is a parameterized dependency: you pass which scopes this endpoint requires.- FastAPI will show scopes in the OpenAPI UI thanks to
oauth2_scheme.
Practical tips & gotchas (read these or cry later)
- Always use HTTPS. If your token travels unencrypted, it's toast.
- Keep access tokens short-lived (e.g., 15 minutes). Use refresh tokens for longer sessions.
- Store refresh tokens server-side (or use rotating refresh tokens) so you can revoke them.
- Don't store JWTs in localStorage for browser apps — prefer secure, httpOnly cookies or a secure client storage pattern.
- Treat the SECRET_KEY like nuclear launch codes. Rotate keys and support key IDs (kid) in JWT headers for smooth rotation.
- Validate not just signature but also audience (aud), issuer (iss), and expiration (exp) if present.
When to use OAuth2 vs third-party providers
- Use built-in OAuth2 + JWT when you manage users and want fine control.
- Use Auth0 / Google / Okta when you want less infra maintenance and are comfortable delegating identity.
- You can combine them: rely on a third-party for login and issue your own JWTs for internal APIs.
Closing — TL;DR + next steps
- OAuth2 = how to get tokens. JWT = the format for tokens.
- FastAPI gives you small, powerful primitives (OAuth2PasswordBearer, OAuth2PasswordRequestForm, SecurityScopes) that play well with DI — so security checks stay modular and testable.
Pick this checklist for your next project:
- Use HTTPS everywhere
- Use short-lived JWT access tokens + refresh tokens
- Put token-verification logic in a shared dependency (reusable guard)
- Use SecurityScopes + parameterized dependencies to protect endpoints with minimal boilerplate
- Store secrets securely and rotate keys
Final thought: security isn't a checkbox — it's a system. Use the DI patterns you already mastered to keep auth logic clean, testable, and reusable. And yes, you can be both secure and sassy.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!