FastAPI Protecting Routes with Dependencies
Authentication tells you who the user is. Authorization tells you what they are allowed to do. This topic shows how to build reusable dependencies that protect routes and enforce permission levels.
The get_current_user Dependency
This single dependency does all the heavy lifting: extracts the token, decodes it, and returns the authenticated user object. Every protected route depends on it.
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from database import get_db
import models
from auth import decode_token ← your JWT decode function
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def get_current_user(
token: str = Depends(oauth2_scheme),
db: Session = Depends(get_db)
):
username = decode_token(token) ← raises 401 if invalid
user = db.query(models.User).filter(models.User.username == username).first()
if user is None:
raise HTTPException(status_code=401, detail="User no longer exists")
return user
Using get_current_user in a Route
@app.get("/me")
def my_profile(current_user = Depends(get_current_user)):
return {"username": current_user.username, "email": current_user.email}
Request without token:
GET /me (no Authorization header)
→ 401 Unauthorized: "Not authenticated"
Request with invalid token:
GET /me Authorization: Bearer bad-token
→ 401 Unauthorized: "Invalid or expired token"
Request with valid token:
GET /me Authorization: Bearer eyJ...
→ 200 OK: {"username": "meera", "email": "m@example.com"}
Adding Role-Based Authorization
Different users have different roles. An admin can delete records. A regular user cannot. Build a dependency that checks the role:
def get_admin_user(current_user = Depends(get_current_user)):
if current_user.role != "admin":
raise HTTPException(
status_code=403,
detail="You do not have permission to perform this action"
)
return current_user
@app.delete("/users/{user_id}")
def delete_user(user_id: int, admin = Depends(get_admin_user)):
# Only runs if current user is admin
...
401 vs 403: 401 Unauthorized → not logged in (no valid token) 403 Forbidden → logged in but lacks permission
Dependency Chain Visualization
delete_user route
│
└── Depends(get_admin_user)
│
└── Depends(get_current_user)
│
├── Depends(oauth2_scheme) → reads token from header
└── Depends(get_db) → opens DB session
FastAPI resolves the chain automatically.
Active/Inactive User Check
Add another layer that blocks deactivated accounts:
def get_active_user(current_user = Depends(get_current_user)):
if not current_user.is_active:
raise HTTPException(status_code=403, detail="Account is deactivated")
return current_user
@app.get("/dashboard")
def dashboard(user = Depends(get_active_user)):
return {"welcome": user.username}
Applying Protection to a Whole Router
Protect every route in a router without adding Depends to each function:
from fastapi import APIRouter, Depends
from auth import get_current_user
router = APIRouter(
prefix="/admin",
tags=["Admin"],
dependencies=[Depends(get_current_user)] ← all routes protected
)
@router.get("/stats")
def admin_stats():
return {"total_users": 1000} ← no Depends needed here
Returning the Current User for Logging
@app.post("/orders")
def place_order(order: OrderSchema, current_user = Depends(get_active_user)):
print(f"Order placed by user ID: {current_user.id}")
# create order linked to current_user.id
return {"order": "created"}
Key Points
- Build a
get_current_userdependency that decodes the token and fetches the user from the database. - Chain dependencies —
get_admin_userbuilds onget_current_user. - Return 401 for missing/invalid tokens and 403 for insufficient permissions.
- Apply router-level
dependenciesto protect all routes in that router at once. - Always check
is_activeto block deactivated accounts from accessing the API.
