---
title: "Protect a FastAPI App"
url: "https://docs.caracal.run/v1.0/guides/protect-fastapi/"
markdown_url: "https://docs.caracal.run/markdown/v1.0/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/v1.0/guides/protect-fastapi/
Markdown URL: https://docs.caracal.run/markdown/v1.0/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.

## Prerequisites

* A Caracal-mandate resource whose audience is `resource://pipernet` or another stable resource ID.
* STS issuer and zone configuration plus a shared production revocation store.
* Public health paths identified before middleware is enabled.

## 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://pipernet",
    revocations=InMemoryRevocationStore(),
    required_scopes=["pipernet:read"],
    routes={
        "/payouts": {"required_scopes": ["pipernet:payout"], "require_delegation": True},
    },
    exclude=["/healthz"],
)


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

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_session`    | Require a governed Session.                                               |
| `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.

Expected result: missing or invalid credentials return `401`, valid but insufficient authority returns `403`, excluded probes remain reachable, and handlers receive `request.state.caracal` only after verification.

:::caution[Failure point: prefix matching]
Route configuration uses path prefixes and the longest match wins. Test overlapping prefixes so a broad rule cannot hide a stricter operation rule.
:::

For every middleware field, use [ASGI Adapter reference](/v1.0/sdks/adapters/asgi/).

## Next Step

Replace the in-memory store with the Redis backend and run the revocation case in [Test Caracal Integrations](/v1.0/guides/testing/).
