Just want one protected route? Start with the [Starlette quickstart](/docs/quickstarts?sdk=starlette).

`authdog.starlette` is ASGI middleware plus async `session` / `require_auth` for **pure Starlette**. FastAPI has its own binding: [FastAPI](/docs/backend/fastapi). Also: [Django](/docs/backend/django), [Flask](/docs/backend/flask), [aiohttp](/docs/backend/aiohttp). Hub: [Python](/docs/backend/python).

## Availability and install

**Unreleased on PyPI.** Source-only in [`packages/python`](https://github.com/authdog/web-sdk/tree/main/packages/python). Python 3.10+, Starlette 0.37+, `httpx` 0.27+.

```bash
python -m pip install "./packages/python[starlette]"
```

## Configure

```python
import os
from authdog.starlette import Authdog

authdog = Authdog(public_key=os.environ["PK_AUTHDOG"])
```

Add `authdog.middleware` to the Starlette app. The public key is publishable. Construction rejects malformed keys and identity hosts outside the trusted HTTPS allowlist.

## Session and gate

Bindings are **async**. `await session(request)` is optional context. `await require_auth(request)` is the 401 boundary.

```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route

async def index(request):
    ctx = await authdog.session(request)
    return JSONResponse({"authenticated": ctx.is_authenticated})

async def me(request):
    user = await authdog.require_auth(request)
    return JSONResponse(user)

async def logout(request):
    return authdog.logout(request)

app = Starlette(
    routes=[
        Route("/", index),
        Route("/me", me),
        Route("/logout", logout),
    ],
    middleware=[authdog.middleware],
)
```

## Shared rules

The resolver prefers `authdog-session`, then `Authorization: Bearer <token>`. `fetch_user=False` leaves `is_authenticated` false, so `require_auth` rejects. Apply [authorization](/docs/concepts/authorization) after the gate.

Self-hosted identity hosts need `AUTHDOG_ALLOWED_IDENTITY_HOSTS`.
