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_user dependency that decodes the token and fetches the user from the database.
  • Chain dependencies — get_admin_user builds on get_current_user.
  • Return 401 for missing/invalid tokens and 403 for insufficient permissions.
  • Apply router-level dependencies to protect all routes in that router at once.
  • Always check is_active to block deactivated accounts from accessing the API.

Leave a Comment

Your email address will not be published. Required fields are marked *