symfonic.agent.fastapi.dependencies¶
dependencies ¶
FastAPI dependencies for tenant scope extraction.
Provides get_tenant_scope as a FastAPI Depends function that
extracts multi-tenant context from HTTP headers.
Security model¶
X-Tenant-ID on its own is untrusted input. The core library cannot
assume a specific auth model (JWT, API key, mTLS, ...), so it exposes a
pluggable async verifier that downstream apps register at startup to
enforce "this caller is allowed to access this tenant".
Two forms exist, and an injected resolver wins over the global wherever both are present -- in this dependency and in the production gate, which have to agree about that or the gate validates something no request will use.
Preferred (what symfonic init generates, see templates/app/main.py.j2):
.. code-block:: python
from symfonic.agent.fastapi.dependencies import install_scope_resolver
from symfonic.platform import HeaderScopeResolver
async def verify_tenant_access(credentials, tenant_id):
# decode JWT, look up user, confirm membership in tenant_id.
# Raise AuthenticationError / AuthorizationError to deny.
return {"principal_id": user.id, "is_admin": user.role == "admin"}
install_scope_resolver(app, HeaderScopeResolver(verifier=verify_tenant_access))
router = create_agent_router(agent, app=app)
Legacy, still supported (a process global holding a per-deployment fact):
.. code-block:: python
from symfonic.agent.fastapi.dependencies import set_tenant_auth_verifier
async def verify_tenant_access(request, tenant_id):
... # raise HTTPException to deny
set_tenant_auth_verifier(verify_tenant_access)
When neither is wired the dependency logs a WARNING once and accepts
X-Tenant-ID on trust -- suitable for tests / dev only, never production,
which is what :func:_check_production_auth_gate refuses to let you ship.
TenantAuthVerifier
module-attribute
¶
Signature: async def verifier(request, tenant_id) -> None.
Implementations should raise fastapi.HTTPException (401 / 403) to deny
the request. Returning None is an explicit allow.
get_scope_resolver ¶
get_tenant_auth_verifier ¶
Return the currently registered verifier, if any.
Exposed primarily for diagnostics / tests.
get_tenant_scope
async
¶
Extract a FrameworkTenantScope from request headers.
Required headers
X-Tenant-ID: Unique tenant identifier (required).
Optional headers
X-Sub-Tenant-ID: Sub-tenant identifier. X-Namespace: Memory namespace.
Security
If a tenant-auth verifier was registered via
set_tenant_auth_verifier it is invoked with the request and
the parsed tenant id. The verifier is expected to confirm the
caller is authenticated AND authorised for the requested tenant,
raising HTTPException(401/403) on denial.
When no verifier is registered a one-shot WARNING is logged and the tenant id is accepted on trust -- acceptable for unit tests and local dev, never for production.
Raises:
| Type | Description |
|---|---|
HTTPException 401
|
Missing X-Tenant-ID header, missing auth, or invalid credentials (propagated from the verifier). |
HTTPException 403
|
Caller is authenticated but not a member of the requested tenant (propagated from the verifier). |
HTTPException 400
|
Invalid scope (e.g. divergent sub_tenant_id/namespace). |
Source code in symfonic/agent/fastapi/dependencies.py
install_scope_resolver ¶
Inject the scope resolver this app derives principals with.
The preferred form. :func:set_tenant_auth_verifier registers a
process global for a per-deployment fact, so two agents mounted in one
process share one verifier and the second one to start silently redefines
authorisation for the first. A resolver held on app.state is scoped to
the app that owns it, which is the whole point of
:class:~symfonic.platform.scope.HeaderScopeResolver.
resolver is anything satisfying
:class:~symfonic.platform.ports.ScopeResolver -- async resolve(
credentials) -> AuthenticatedPrincipal.
Note the two verifier protocols are not interchangeable. The legacy one
takes (Request, tenant_id) and denies by raising HTTPException; the
platform one takes (RequestCredentials, tenant_id) and returns
principal facts. A legacy verifier therefore cannot be wrapped into a
resolver without losing its access to the request, so this function does not
try: an app either injects a resolver or keeps its verifier, and
:func:get_tenant_scope honours whichever is present.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
Any
|
The |
required |
resolver
|
Any
|
The resolver, or |
required |
Source code in symfonic/agent/fastapi/dependencies.py
set_tenant_auth_verifier ¶
Register (or clear) the tenant-access verifier.
Call this once at app startup, before mounting the agent router.
Passing None unregisters the current verifier (useful for tests).
The verifier receives the raw Request plus the parsed tenant_id
from the X-Tenant-ID header and must raise an HTTPException to
deny access. Any other exception is treated as a 500 by FastAPI.