Authentication & Authorization
FastPluggy ships a minimal auth layer built on Starlette's AuthenticationMiddleware.
Auth backends are provided by plugins (e.g. auth_user) and auto-wired at startup.
How it works
- No auth plugin installed → all routes are open,
request.state.current_userisFPAnonymousUser - Auth plugin installed (e.g.
auth_user) → the plugin callsfast_pluggy.set_auth_manager(backend)duringon_load_complete, which sets the backend that the always-installedAuthenticationMiddlewaredelegates to (populatingrequest.user) and flips theauth_enabletemplate global. It does NOT gate anything.
⚠ Installing an auth plugin gates NOTHING by itself.
AuthenticationMiddlewareidentifies the caller; it never rejects one. Every protected surface must opt in withDepends(require_authentication)/require_role(...)— per route or perinclude_router. An app that loadsauth_userand adds no dependency is fully open to anonymous users.
Verifying a gate — test it, don't inspect it
Since FastAPI >= 0.138 include_router(..., dependencies=[...]) no longer bakes the
dependencies into the route objects; it wraps them in an _IncludedRouter and resolves them at
request time. Walking router.routes / route.dependencies therefore shows the original
dependency-free routes and cannot tell you what is gated — an inspection-based test reports
"0 routes gated" against a gate that demonstrably works, and would just as happily mis-report a
broken one. Assert behaviourally instead:
r = TestClient(app).get("/plugin/thing", follow_redirects=False)
assert r.status_code in (401, 403, 307) # anonymous is refused
You can also pass an auth manager explicitly:
If set explicitly, plugins will not override it.
set_auth_manager(backend)
Sets the auth backend at runtime. Called automatically by auth plugins, but can also be used programmatically:
This:
- Sets self.auth_manager
- Updates the auth_enable template global
It adds no middleware. AuthenticationMiddleware is installed once,
unconditionally, in FastPluggy.__init__, with a DelegatingAuthBackend that re-reads
fastpluggy.auth_manager on every request — so set_auth_manager is a plain assignment.
That is load-bearing, not incidental. Starlette freezes app.add_middleware() after the
first ASGI call, so the older "add the middleware once the backend arrives" shape could
only ever work for a plugin that loaded before the app started serving. Because the
middleware is now a fixed point and only the attribute it reads changes,
set_auth_manager is safe to call at any time, more than once, and from a degraded-mode
self-heal long after the app has been answering requests (fast_pluggy#22).
Dependencies
require_authentication
Rejects unauthenticated requests (401) or redirects to login if the auth backend
defines on_authenticate_error. When no auth manager is set, this is a no-op —
all requests pass through.
# Protect an entire router
app.include_router(my_router, dependencies=[Depends(require_authentication)])
# Or a single route
@router.get("/secret", dependencies=[Depends(require_authentication)])
async def secret(request: Request): ...
require_role(role: str)
Requires the user to have a specific role (checked against request.auth.scopes).
FastPluggy uses "fp_admin" to protect its own admin routes. No-op when auth manager
is not set.
Current user
Set by CurrentUserMiddleware on every request:
user = request.state.current_user # FPAnonymousUser if not authenticated
if user.is_authenticated:
...
if user.is_admin:
...
Available in templates as {{ request.state.current_user }}.
FPAnonymousUser extends Starlette's UnauthenticatedUser and is used as the
fallback when no user is authenticated. It guarantees request.state.current_user
is never None.
The user object shape depends on the auth backend. FastPluggy core expects:
| Attribute | Type | Description |
|---|---|---|
is_authenticated |
bool |
False for anonymous, True for logged-in users |
display_name |
str |
Shown in the topbar user menu |
is_admin |
bool |
Controls visibility of the admin sidebar section |
profile_picture |
str\|None |
Optional; used for avatar display |
Writing a custom auth backend
Implement AuthInterface (extends Starlette's AuthenticationBackend):
from fastpluggy.core.auth.auth_interface import AuthInterface
class MyAuthManager(AuthInterface):
@property
def user_model(self):
return MyUserModel
async def authenticate(self, conn):
# Return (AuthCredentials, user) or None
...
async def on_authenticate_error(self, request):
# Return a Response (e.g. redirect) or raise HTTPException
...
Then either pass it to FastPluggy(app, auth_manager=MyAuthManager()) or call
fast_pluggy.set_auth_manager(MyAuthManager()) from a plugin's on_load_complete.