---
title: "Protect a FastAPI App"
url: "https://docs.caracal.run/guides/protect-fastapi/"
markdown_url: "https://docs.caracal.run/markdown/guides/protect-fastapi.md"
description: "Add the CaracalASGIAuth middleware to FastAPI or any ASGI app to verify mandates and enforce scope requirements."
page_type: "page"
concepts: []
requires: []
---

# Protect a FastAPI App

Canonical URL: https://docs.caracal.run/guides/protect-fastapi/
Markdown URL: https://docs.caracal.run/markdown/guides/protect-fastapi.md
Description: Add the CaracalASGIAuth middleware to FastAPI or any ASGI app to verify mandates and enforce scope requirements.
Page type: page
Concepts: none
Requires: none

---

Use `caracalai-asgi` when a FastAPI, Starlette, or other ASGI app should verify Caracal mandates before request handlers run. This is the provider-side boundary: a partner serving Caracal-governed customers verifies each inbound mandate against the zone's keys before doing any work.

## Install

```bash
pip install caracalai-asgi caracalai-revocation
```

Use a Redis-backed revocation store in production. The in-memory store is only suitable for local development and tests.

## Add middleware

```python
from caracalai_asgi import CaracalASGIAuth
from caracalai_revocation import InMemoryRevocationStore
from fastapi import FastAPI, Request

app = FastAPI()
app.add_middleware(
    CaracalASGIAuth,
    audience="resource://billing-api",
    revocations=InMemoryRevocationStore(),
    required_scopes=["billing:read"],
    routes={
        "/payouts": {"required_scopes": ["billing:payout"], "require_delegation": True},
    },
    exclude=["/healthz"],
)


@app.post("/payouts/create")
async def create_payout(request: Request):
    principal = request.state.caracal
    return {"actor": principal.sub, "agent": principal.agent_session_id}
```

With `CARACAL_STS_URL` and `CARACAL_ZONE_ID` set — the standard Caracal workload variables — the middleware resolves the issuer and zone itself; you state only your own audience and revocation store. Requests reach your handlers only after the mandate's signature, issuer, audience, zone, token use, scopes, and revocation anchors all verify.

## Enforce the right boundary

| Option | Use it for |
| --- | --- |
| `required_scopes` | Route-level scope checks. |
| `required_targets` | Resource-target checks. |
| `require_agent` | Reject non-agent mandates. |
| `require_delegation` | Require delegated authority. |
| `max_hop_count` | Limit delegation depth. |
| `routes` | Apply any of the above per path prefix; the longest matching prefix wins. |

## Validate

1. Exchange for a mandate that targets the resource.
2. Call the protected route with `Authorization: Bearer <mandate>`.
3. Remove a required scope and confirm the route returns `403`.
4. Revoke the session and confirm the route rejects the old mandate.

Related pages: [Mandates](/concepts/mandate/) and [Sessions and Revocation](/concepts/sessions-revocation/).
