# mgf-auth

Two **ISP-split auth verifier seams** for `mgf-common` consumers — a
generic `SessionVerifier` + `WebhookVerifier`, a **Clerk** adapter
(session-JWT verification + Svix-webhook verification), typed 401 errors,
and test mocks. A sibling of `mgf-common` under the `mgf.*` namespace.

Extracted from PlasmaMapper after its core re-design ISP-split the auth
provider (ADR-016). A consumer keeps its own role-typed principal —
`SessionVerifier` is **generic over the principal type**; identity
resolution (Clerk ids → your principal) is your `principal_resolver`.

## Install

```bash
pip install "mgf-auth[clerk]"   # [clerk] adds pyjwt for the session adapter
```

## Use

```python
from mgf.auth.clerk_session import ClerkSessionVerifier, ClerkClaims
from mgf.auth.clerk_webhook import ClerkWebhookVerifier

async def resolve(claims: ClerkClaims) -> MyPrincipal:
    return MyPrincipal(user_id=uuid5(NS, claims.sub), role=map_role(claims.org_role), ...)

session_verifier: ClerkSessionVerifier[MyPrincipal] = ClerkSessionVerifier(
    jwt_public_key=PEM, issuer="https://clerk.acme.example", principal_resolver=resolve,
)
principal = await session_verifier.verify_session(bearer_token)   # -> MyPrincipal

webhook_verifier = ClerkWebhookVerifier(webhook_secret=b"whsec_...")
event = await webhook_verifier.verify_webhook(headers, raw_body)   # -> WebhookEvent
```

Tests use `MockSessionVerifier[MyPrincipal]()` (`add_session`) and
`MockWebhookVerifier()` (`configure_webhook_secret` + the `sign()`
helper) — no Clerk, no network.

## What's in it

| Name | What |
|---|---|
| `SessionVerifier` / `WebhookVerifier` | the two `@runtime_checkable` seams (session is generic over `P`) |
| `WebhookEvent` | the verified-webhook DTO (`event_type`, `event_id`, `payload`, `received_at`) |
| `InvalidSessionError` / `InvalidWebhookSignatureError` | typed **401** errors (subclass `mgf.common` `HttpUnauthorizedError`) |
| `mgf.auth.clerk_session.ClerkSessionVerifier` / `ClerkClaims` | PyJWT session verification (`[clerk]` extra) |
| `mgf.auth.clerk_webhook.ClerkWebhookVerifier` | Svix-Signature HMAC webhook verification (stdlib) |
| `MockSessionVerifier` / `MockWebhookVerifier` | in-process test doubles |

## Design

- **Generic over the principal** (`SessionVerifier[P]`): the JWT
  mechanics live here; the consumer's `principal_resolver` produces its
  own principal shape — no imposed role/identity model.
- **Vendor SDK behind an extra**: importing `mgf.auth` pulls no `pyjwt`
  (import-linter-enforced); the Clerk session adapter is the only place
  that needs it.
- **Webhook side is stdlib-only** (HMAC-SHA256 over `<id>.<ts>.<body>`).
- Session revocation (checking a `revoked_sessions` table) is a
  **consumer concern** — it's DB-coupled and app-specific, so it stays
  in the consumer, not here.
