FastAPI Quickstart
Wire LumoAuth into a FastAPI app using the official
lumoauth.fastapi router. One
include_router(...) mounts /login, /callback, /logout; the
get_current_user dependency is everything else.
Prerequisites
- Python 3.10+
- An OAuth client registered with redirect URI
http://localhost:8000/auth/callback.
Register the client in the portal under Applications → New application: enter an Application name, choose Web application, enter the Redirect URI, and click Create application. The client secret is shown once, on the page you land on.

The Quickstarts page (under Applications & developer tools) has a FastAPI tab that shows the .env values for your organization and client pre-filled, ready to copy.

The tab's first step lists only OAuth clients of the kind these snippets need; if none exists, Create one for this framework opens the create form with the right type preselected and brings you back. Its last step, Check it worked, shows the first sign-in that completes through the selected client.
Or scaffold with the CLI:
lumo login --org your-org-slug
lumo init --framework fastapi --dir ./my-app
cd ./my-app && python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt && uvicorn main:app --reload --port 3000
Manual install
pip install 'lumoauth[fastapi]' uvicorn[standard] python-dotenv itsdangerous
.env:
LUMO_BASE_URL=https://app.lumoauth.dev
LUMO_ORG_ID=your-org-slug
LUMO_CLIENT_ID=your-oauth-client-id
LUMO_CLIENT_SECRET=your-oauth-client-secret
SESSION_SECRET=change-me-to-a-random-32-byte-string
main.py:
import os
from dotenv import load_dotenv
from fastapi import FastAPI, Depends
from fastapi.responses import RedirectResponse, HTMLResponse
from starlette.middleware.sessions import SessionMiddleware
from lumoauth.fastapi import (
lumo_auth_router,
get_current_user,
require_auth,
User,
)
load_dotenv()
app = FastAPI()
# SessionMiddleware MUST be mounted before lumo_auth_router.
app.add_middleware(SessionMiddleware, secret_key=os.environ["SESSION_SECRET"])
app.include_router(
lumo_auth_router(
base_url=os.environ["LUMO_BASE_URL"],
organization=os.environ["LUMO_ORG_ID"],
client_id=os.environ["LUMO_CLIENT_ID"],
client_secret=os.environ.get("LUMO_CLIENT_SECRET"), # omit for public
callback_path="/auth/callback",
),
prefix="/auth",
)
@app.get("/", response_class=HTMLResponse)
def home(user: User | None = Depends(get_current_user)):
if user is None:
return RedirectResponse("/auth/login")
return f"<h1>Hi {user.email}</h1><a href='/auth/logout'>Sign out</a>"
@app.get("/api/me")
def me(user: User = Depends(require_auth())):
return user.model_dump()
Run with uvicorn main:app --reload, visit
http://localhost:8000 — you'll be redirected to LumoAuth, sign in,
and bounce back authenticated.
Optional vs required auth
# Optional — works for both anonymous and authenticated callers.
@app.get("/dashboard")
def dashboard(user: User | None = Depends(get_current_user)):
if user is None:
return RedirectResponse("/auth/login")
return {"name": user.name}
# Required — 401 if no session.
@app.get("/api/me")
def me(user: User = Depends(require_auth())):
return user.model_dump()
# Required + scope check — 403 if any required scope is missing.
@app.get("/api/admin")
def admin(user: User = Depends(require_auth(scopes=["admin"]))):
return {"ok": True}
Reading custom claims
The User model uses Pydantic v2 with extra="allow", so any custom
claims your organization adds to /userinfo (org-specific traits, custom
scopes, etc.) come through as attributes:
@app.get("/api/me")
def me(user: User = Depends(require_auth())):
return {
"email": user.email,
"name": user.name,
# Standard claims are typed
"department": getattr(user, "department", None),
# Custom claims fall through model_dump()
"all_claims": user.model_dump(),
}
Reference
lumoauth.fastapiAPI reference- Webhooks — pair with
lumo tunnelto forward events to your local FastAPI handler during development. - The
lumoauthPython SDK proper — for AAuth / agent flows / RBAC checks from inside route handlers.