The clinic portal returns today's appointments on `GET /appointments`. Anonymous callers are rejected.

Clone [starters/django](https://github.com/authdog/samples/tree/main/starters/django). SDK reference: [Django](/docs/backend/django).

`require_auth` is the identity gate. A signed-in session is not a permission grant. Apply [authorization](/docs/concepts/authorization) after the gate.

## What you need

| Requirement | Detail |
| --- | --- |
| Public key | `pk_...` from the [console](https://console.authdog.com) |
| Environment | Copy `.env.example` to `.env` and export `PK_AUTHDOG` |
| Secret key | Never put `sk_...` in the service |
| Package | Django extra is source in [authdog/web-sdk](https://github.com/authdog/web-sdk) `packages/python`. It is not the `authdog` package on PyPI. Python 3.10+ |
| Request | Hosted sign-in stays in a browser. Call `/appointments` with the `authdog-session` cookie or `Authorization: Bearer` |

## Run

```bash
python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
export PK_AUTHDOG=pk_...
python manage.py runserver 3000
```

With a valid session the body includes `apt_441` (Riverside, 09:10, new referral) and `apt_442` (Riverside, 10:40, follow-up).

## Appointments

Register the middleware, then decorate the private view.

```python
# clinic/settings.py
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.middleware.common.CommonMiddleware",
    "clinic.auth.authdog.middleware",
]
```

```python
# clinic/auth.py
import os

from authdog.django import Authdog

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

```python
# clinic/views.py
from django.http import JsonResponse

from clinic.auth import authdog

@authdog.require_auth
def appointments(request):
    return JsonResponse(
        {
            "caller": authdog.session(request).user,
            "appointments": [
                {"id": "apt_441", "clinic": "Riverside", "slot": "09:10", "reason": "New referral"},
                {"id": "apt_442", "clinic": "Riverside", "slot": "10:40", "reason": "Follow-up"},
            ],
        }
    )
```

`fetch_user=False` leaves `is_authenticated` false, so the decorator rejects.
