FastAPI integration
The FastAPI adapter provides a session dependency that owns the transaction and applies the caller's identity. This is the recommended integration point.
The session dependency
rls.session_dependency(identity=…) returns a FastAPI dependency that yields an
RLS-scoped Session. You supply an identity callable — itself a FastAPI
dependency — that returns the context mapping.
from fastapi import Depends, FastAPI
from sqlalchemy import select
from sqlalchemy.orm import Session
from fastapi_rls import RLS
rls = RLS(engine=engine)
def identity(user=Depends(get_current_user)) -> dict:
# fastapi-rls never authenticates. You resolve the principal; it propagates.
return {"tenant_id": user.tenant_id}
get_session = rls.session_dependency(identity=identity)
app = FastAPI()
@app.get("/documents")
def list_documents(session: Session = Depends(get_session)):
return session.scalars(select(Document)).all()
Under the hood the dependency:
- opens a session and
session.begin()transaction, - applies your identity with
SET LOCAL(is_local=True), - calls
require_rls_context()when configured to, - yields the session, and
- commits — discarding the
LOCALcontext.
None and empty-string identity values are filtered out, so returning
{"tenant_id": None} sets nothing (and, with require_context=True, fails
loudly rather than silently seeing nothing).
Resolving identity
The identity callable is the heart of the integration and the security-critical
line. Derive it from a verified principal — a validated JWT claim or a
server-side session — never from an unverified header an attacker controls.
def identity(claims=Depends(verified_jwt)) -> dict:
return {"tenant_id": claims["org_id"], "user_id": claims["sub"]}
Multiple keys are fine; each becomes a SET LOCAL rls.<key>.
Optional: identity middleware
Sometimes you want to resolve "who is the principal" in one central place. The
RLSContextMiddleware runs an extractor(request) once and stashes the result
on request.state. It deliberately does not touch the database — a FastAPI
request creates its session inside the route, on a connection the middleware
doesn't hold, so setting a session variable from middleware would target the
wrong connection.
Pair it with identity_from_state(), which reads what the middleware stored:
from fastapi_rls.middleware import RLSContextMiddleware, identity_from_state
def extract_identity(request) -> dict:
return {"tenant_id": request.headers.get("X-Org-Id")} # from a *verified* source
app.add_middleware(RLSContextMiddleware, extractor=extract_identity)
get_session = rls.session_dependency(identity=identity_from_state())
The middleware centralizes extraction; the dependency remains the single writer
of database context. The extractor may be sync or async.
Reading identity in a handler
Read identity from your own dependency, not from get_active_rls_context().
FastAPI runs sync dependencies and sync handlers in different threadpool
contexts, so the contextvar mirror isn't reliable in a sync handler. Enforcement
is unaffected — it rides the session's SET LOCAL. If a handler needs the
tenant id as a value, inject it:
@app.get("/whoami")
def whoami(ident: dict = Depends(identity)):
return ident