Skip to main content

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:

  1. opens a session and session.begin() transaction,
  2. applies your identity with SET LOCAL (is_local=True),
  3. calls require_rls_context() when configured to,
  4. yields the session, and
  5. commits — discarding the LOCAL context.

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

Next: async, or apply policies via the CLI / Alembic.