OutlabsAuth
Build

Authorization Dependencies

Protect host routes with authentication, permission, entity, tree, source, and two-phase checks.

OutlabsAuth exposes FastAPI dependencies through auth.deps. They authenticate the configured credential sources, apply account-state rules, and keep the authorization decision inside the library.

For an ordinary route, use require_auth(...) or require_permission(...). The two-phase API later on this page is for infrastructure components that must authenticate once and authorize several resources in one request.

Authenticate a caller

routes.py
from fastapi import Depends


@app.get("/profile")
async def profile(ctx: dict = Depends(auth.deps.require_auth())):
    return {"authenticated": True, "source": ctx["source"]}

require_auth(active=True, verified=False, optional=False) accepts any configured credential source. Set verified=True when the route requires a verified user. Set optional=True only when the route deliberately supports an anonymous result.

The returned dictionary is an auth-owned context, not a public scope format. It is safe to use identity fields for application behavior; do not grant access by interpreting raw permission or scope metadata from it.

Require permissions

routes.py
@app.get("/reports")
async def read_reports(
    ctx: dict = Depends(auth.deps.require_permission("report:read")),
):
    return {"authorized": True, "source": ctx["source"]}


@app.post("/reports/export")
async def export_reports(
    ctx: dict = Depends(
        auth.deps.require_permission(
            "report:read",
            "report:export",
            require_all=True,
        )
    ),
):
    ...

Multiple permissions mean “any” by default. Pass require_all=True when every permission is required. Authentication failures return 401; authenticated callers without the grant receive 403.

Entity and tree checks

EnterpriseRBAC adds context-aware checks:

routes.py
@app.get("/entities/{entity_id}/billing")
async def entity_billing(
    entity_id: UUID,
    ctx: dict = Depends(
        auth.deps.require_entity_permission(
            "billing:read",
            entity_id_param="entity_id",
        )
    ),
):
    ...


@app.get("/entities/{entity_id}/descendants")
async def descendants(
    entity_id: UUID,
    ctx: dict = Depends(
        auth.deps.require_tree_permission(
            "entity:read_tree",
            "entity_id",
            source="path",
        )
    ),
):
    ...

require_entity_permission resolves the named path parameter and checks inside that entity. require_tree_permission supports source="path", "query", or "header"; use the path form when possible because the protected resource is explicit in the route.

Restrict credential types

routes.py
@app.post("/webhooks/import")
async def import_webhook(
    ctx: dict = Depends(auth.deps.require_source("api_key")),
):
    ...


@app.post("/system/reindex")
async def reindex(
    ctx: dict = Depends(auth.deps.require_superuser()),
):
    ...

require_source accepts one of the configured backend names such as jwt, api_key, or service_token. require_superuser authenticates first, then requires the user’s superuser flag.

Authenticate once, authorize several resources

Added in 0.1.0a26, authorize_authenticated(...) is the supported boundary for an infrastructure component that authenticates once and then checks several resources. It does not re-enter a credential backend or record API-key usage a second time.

infrastructure.py
ctx = await auth.deps.require_auth()(
    request=request,
    session=auth_session,
)

if auth.deps.authenticated_authorization_requires_session(ctx):
    async with auth.session_factory() as policy_session:
        await auth.deps.authorize_authenticated(
            request,
            ctx,
            "queue_a:run",
            "queue_b:run",
            require_all=True,
            session=policy_session,
        )
else:
    await auth.deps.authorize_authenticated(
        request,
        ctx,
        "queue_a:run",
        "queue_b:run",
        require_all=True,
    )

The session helper is deliberately conservative: user and JWT identities need a policy session; service tokens do not; integration principals may avoid one only when entity traversal and ABAC are not involved. If the component already owns a suitable request-scoped session, it may pass that session directly.

authorize_authenticated(...) also accepts entity_id and resource_context_provider, matching the entity and ABAC behavior of require_permission(...). The authenticated context must belong to the same request; a context copied from another request is rejected.

Choose the right API

NeedUse
Any valid configured credentialrequire_auth()
One or more grantsrequire_permission(...)
A grant inside one entityrequire_entity_permission(...)
A grant across an entity subtreerequire_tree_permission(...)
One credential source onlyrequire_source(...)
A human superuserrequire_superuser()
Authenticate once, authorize many resourcesauthorize_authenticated(...)