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 { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
typescript
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi';
Validating env config at boot in NestJS
configuration
schema-validation
environment-variables
Intermediate
8 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { InjectRepository } from '@nestjs/typeorm';
A cache-aside country lookup in NestJS
cache-aside
dependency-injection
batch-lookup
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.