python 56 lines · 7 steps

Chaining tenant dependencies in FastAPI

A multi-tenant FastAPI app resolves the tenant, authenticates the user, and confirms membership by composing three async dependencies.

Explained by highlit
1from fastapi import Depends, FastAPI, Header, HTTPException, status
2from sqlalchemy import select
3from sqlalchemy.ext.asyncio import AsyncSession
4 
5from .database import get_session
6from .models import Membership, Tenant, User
7from .security import decode_access_token
8 
9app = FastAPI()
10 
11 
12async def resolve_tenant(
13 host: str = Header(...),
14 session: AsyncSession = Depends(get_session),
15) -> Tenant:
16 subdomain = host.split(":")[0].split(".")[0]
17 if subdomain in ("www", "app", ""):
18 raise HTTPException(status.HTTP_400_BAD_REQUEST, "Missing tenant subdomain")
19 
20 tenant = await session.scalar(
21 select(Tenant).where(Tenant.slug == subdomain, Tenant.is_active.is_(True))
22 )
23 if tenant is None:
24 raise HTTPException(status.HTTP_404_NOT_FOUND, "Unknown tenant")
25 return tenant
26 
27 
28async def current_user(
29 authorization: str = Header(...),
30 session: AsyncSession = Depends(get_session),
31) -> User:
32 scheme, _, token = authorization.partition(" ")
33 if scheme.lower() != "bearer" or not token:
34 raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid auth header")
35 
36 payload = decode_access_token(token)
37 user = await session.get(User, payload["sub"])
38 if user is None:
39 raise HTTPException(status.HTTP_401_UNAUTHORIZED, "User not found")
40 return user
41 
42 
43async def tenant_scope(
44 tenant: Tenant = Depends(resolve_tenant),
45 user: User = Depends(current_user),
46 session: AsyncSession = Depends(get_session),
47) -> Membership:
48 membership = await session.scalar(
49 select(Membership).where(
50 Membership.tenant_id == tenant.id,
51 Membership.user_id == user.id,
52 )
53 )
54 if membership is None:
55 raise HTTPException(status.HTTP_403_FORBIDDEN, "Not a member of this tenant")
56 return membership
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1FastAPI dependencies can depend on other dependencies, letting you build authorization from small, reusable, independently testable pieces.
  2. 2Deriving the tenant from the request Host header keeps tenant selection implicit and out of every route signature.
  3. 3Raising HTTPException with precise status codes turns each validation stage into a clear, self-documenting failure boundary.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Chaining tenant dependencies in FastAPI — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code