python
56 lines · 8 steps
Chaining auth dependencies in FastAPI
A layered chain of FastAPI dependencies decodes a JWT, loads the user, enforces an admin role, then guards a delete endpoint.
Explained by
highlit
1from fastapi import APIRouter, Depends, HTTPException, status
2from fastapi.security import OAuth2PasswordBearer
3from jose import JWTError, jwt
4from sqlalchemy.orm import Session
5
6from .database import get_db
7from .models import User
8from .config import settings
9
10router = APIRouter(prefix="/admin", tags=["admin"])
11oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")
12
13
14def get_current_user(
15 token: str = Depends(oauth2_scheme),
16 db: Session = Depends(get_db),
17) -> User:
18 credentials_error = HTTPException(
19 status_code=status.HTTP_401_UNAUTHORIZED,
20 detail="Could not validate credentials",
21 headers={"WWW-Authenticate": "Bearer"},
22 )
23 try:
24 payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
25 user_id = payload.get("sub")
26 if user_id is None:
27 raise credentials_error
28 except JWTError:
29 raise credentials_error
30
31 user = db.query(User).filter(User.id == int(user_id)).first()
32 if user is None:
33 raise credentials_error
34 return user
35
36
37def require_admin(current_user: User = Depends(get_current_user)) -> User:
38 if current_user.role != "admin":
39 raise HTTPException(
40 status_code=status.HTTP_403_FORBIDDEN,
41 detail="Admin privileges required",
42 )
43 return current_user
44
45
46@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
47def delete_user(
48 user_id: int,
49 admin: User = Depends(require_admin),
50 db: Session = Depends(get_db),
51) -> None:
52 target = db.query(User).filter(User.id == user_id).first()
53 if target is None:
54 raise HTTPException(status_code=404, detail="User not found")
55 db.delete(target)
56 db.commit()
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1FastAPI dependencies compose: one dependency can depend on another to build layered access checks.
- 2Decode and validate a JWT once in a reusable dependency, then reuse it everywhere via Depends.
- 3Return the same generic 401 for every failure mode so attackers can't distinguish causes.
Related explainers
typescript
import { CanActivate, ExecutionContext, Injectable,
How guards chain auth in NestJS
guards
authentication
authorization
Intermediate
8 steps
python
from flask import Blueprint, request, url_for, jsonify, abort from flask.views import MethodView from .models import Article, db
Building a paginated JSON API with Flask MethodView
rest-api
pagination
class-based-views
Intermediate
10 steps
python
import threading from functools import wraps
A thread-safe debounce decorator in Python
decorators
debounce
threading
Advanced
6 steps
python
from urllib.parse import urlparse, parse_qs from dataclasses import dataclass, field ALLOWED_SCHEMES = {"http", "https"}
Validating URLs into a safe endpoint in Python
input validation
url parsing
dataclasses
Intermediate
7 steps
typescript
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { BullModule } from '@nestjs/bullmq';
How a NestJS feature module wires up
dependency-injection
modularity
orm
Intermediate
7 steps
python
from flask import Blueprint, render_template, request, redirect, url_for, flash from werkzeug.security import check_password_hash from .models import User, db
A change-email route in Flask
blueprints
form-validation
password-hashing
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/chaining-auth-dependencies-in-fastapi-explained-python-7591/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.