Metadata-Version: 2.4
Name: oxaigen-auth
Version: 0.0.6
Summary: Oxaigen Auth SDK
Author: Luca Roggeveen
Author-email: luca@oxaigen.com
Requires-Python: >=3.10,<3.13
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Dist: fastapi (>=0.118)
Requires-Dist: httpx (>=0.28.1)
Requires-Dist: pydantic (>=2.0)
Requires-Dist: python-multipart (>=0.0.28,<0.0.29)
Description-Content-Type: text/markdown

# Oxaigen Auth SDK Guide

This project is a minimal FastAPI backend that demonstrates how to protect endpoints with the `oxaigen-auth` SDK.

Use this document as a shareable integration guide for teams that want to add Oxaigen authentication to their own
backend app.

## What this SDK gives you

- Request middleware that enriches each request with auth context.
- A `get_current_user` dependency for authenticated routes.
- A `get_current_user_optional` dependency for mixed public/private routes.
- A `require_permission("<permission>")` dependency for role/permission checks.
- A typed `User` model injected into your endpoint handlers.

## Requirements

- Python `3.11+`
- FastAPI app
- An Oxaigen environment/proxy available for token verification

## Install

With Poetry:

```bash
poetry add oxaigen-auth
```

With pip:

```bash
pip install oxaigen-auth
```

## Import paths (recommended)

```python
from oxaigen import (
    User,
    get_current_user,
    get_current_user_optional,
    require_permission,
    OxaigenAuthMiddleware,
)
```

Available root exports include:

- Main API: `User`, `get_current_user`, `get_current_user_optional`, `require_permission`, `OxaigenAuthMiddleware`
- Settings and exceptions: `OxaigenAuthSettings`, `OxaigenAuthUnauthenticatedError`, `OxaigenAuthPermissionDeniedError`,
  `OxaigenAuthUpstreamError`
- Advanced utilities: `ProxyAuthClient`, `aclose_client`, `extract_bearer_token`, `derive_proxy_base_url`

## Minimal FastAPI integration

```python
from typing import Optional
from fastapi import FastAPI, Depends
from oxaigen_auth import OxaigenAuthMiddleware
from oxaigen_auth import (
    User,
    get_current_user,
    get_current_user_optional,
    require_permission,
)

app = FastAPI()
app.add_middleware(OxaigenAuthMiddleware)


@app.get("/v1/me")
async def me(user: User = Depends(get_current_user)):
    return user


@app.get("/v1/maybe-public")
async def maybe_public(user: Optional[User] = Depends(get_current_user_optional)):
    if user:
        return {"signed_in": True, "email": user.email}
    return {"signed_in": False}


@app.get("/v1/audit")
async def audit(user: User = Depends(require_permission("audit"))):
    return {"audit_log": [{"hello": "world"}]}
```

## Behavior model

Important: middleware and dependencies have different responsibilities.

- `OxaigenAuthMiddleware`:
    - Parses incoming auth context.
    - Enriches request state with user/auth metadata when present.
    - Does **not** block unauthenticated requests on its own.

- `get_current_user`:
    - Enforces authentication.
    - Returns the authenticated `User`.
    - Fails the request when token/user validation fails.

- `get_current_user_optional`:
    - Returns `User` when token is valid.
    - Returns `None` when token is missing/invalid.
    - Still fails with `502` when auth upstream is unavailable.

- `require_permission("...")`:
    - Enforces both authentication and permission presence.
    - Use for routes that require explicit capabilities (for example `audit`).
    - Validates permission name format: `[A-Za-z0-9_-]+`.

## Authenticated request flow

1. Client sends request with auth cookie/token.
2. Middleware runs and prepares auth request context.
3. Endpoint dependency (`get_current_user` or `require_permission`) validates against Oxaigen auth backend.
4. Endpoint executes with resolved `User`.

Token extraction order:

1. `Authorization: Bearer <token>` header
2. Auth cookie (configured name)

## Caching behavior

The SDK caches per process to reduce upstream traffic:

- User cache: token hash -> `User` (`CACHE_TTL_SECONDS`, default `30s`)
- Permission cache: token hash + permission -> bool (`CACHE_TTL_SECONDS`, default `30s`)
- Negative cache for invalid tokens: token hash sentinel (`NEGATIVE_CACHE_TTL_SECONDS`, default `5s`)

Notes:

- Cache keys use a hash of the token (not the raw token).
- Invalid token bursts are throttled by the negative cache.
- Middleware and dependencies share the same validation/cache pipeline.

## SDK configuration (optional)

All env vars are optional with safe defaults:

| Var                                       | Default                          | Purpose                                                          |
|-------------------------------------------|----------------------------------|------------------------------------------------------------------|
| `OXAIGEN_AUTH_PROXY_URL_OVERRIDE`         | unset                            | Force a specific proxy URL. Default: derived from `Host` header. |
| `OXAIGEN_AUTH_PROXY_SCHEME`               | `https`                          | Scheme when deriving proxy URL. Set `http` for dev.              |
| `OXAIGEN_AUTH_ACCESS_TOKEN_COOKIE_NAME`   | `OxaigenPlatformAuthAccessToken` | Match if the proxy uses a custom cookie name.                    |
| `OXAIGEN_AUTH_CACHE_TTL_SECONDS`          | `30`                             | Validation cache TTL.                                            |
| `OXAIGEN_AUTH_NEGATIVE_CACHE_TTL_SECONDS` | `5`                              | TTL for failed-validation entries.                               |
| `OXAIGEN_AUTH_CACHE_MAX_ENTRIES`          | `1024`                           | LRU cap.                                                         |
| `OXAIGEN_AUTH_PROXY_TIMEOUT_SECONDS`      | `5.0`                            | Server-to-server HTTP timeout.                                   |

Tip: for local/dev environments behind non-standard host routing, `OXAIGEN_AUTH_PROXY_URL_OVERRIDE` is commonly the most
useful setting.

## Local auth mock server

The package ships a development mock at `oxaigen_auth/dev/mock_server.py` that emulates the proxy's `/_oxa_auth/*`
surface (login, callback, token exchange, `get-me`, permission checks) so you can run your backend end-to-end without a
real proxy or Keycloak. Start it with `uv run oxaigen-auth-mock` (or `poetry run oxaigen-auth-mock`).

See [`docs/DEVELOPMENT.md`](./oxaigen_auth/docs/DEVELOPMENT.md) for the full setup: built-in personas, env vars, frontend dev-server
proxy config, and troubleshooting.

## Advanced usage

Most apps only need dependencies + middleware. Advanced consumers can use:

- `ProxyAuthClient` for direct low-level proxy calls
- `extract_bearer_token` and `derive_proxy_base_url` for custom flows
- `aclose_client` to close SDK HTTP resources during app shutdown

## Production recommendations

- Keep `/health` public; protect business endpoints with dependencies.
- Use short timeout/cache defaults unless you have measured reasons to change.
- Add integration tests for:
    - unauthenticated access (expect failure),
    - authenticated access (expect success),
    - missing permission (expect failure),
    - required permission present (expect success).

## Deployment expectations

Your app must be served behind the Oxaigen proxy so `/_oxa_auth/*` endpoints are available on the same host as the
frontend.

If those endpoints are unavailable, SDK auth checks cannot succeed.

---

## Local development setup (two-terminal flow)

When developing a backend app that uses the Oxaigen Auth SDK, you typically run **two processes side by side**:

1. The **auth mock server** — emulates the proxy's `/_oxa_auth/*` endpoints that the SDK calls to validate tokens.
2. Your **backend application** — the FastAPI app that imports the SDK.

This is necessary because in production, your app sits behind the Oxaigen proxy and the SDK resolves `/_oxa_auth/*` on
the request's own `Host`. Locally there is no proxy in front, so the SDK needs to be told to call the mock instead.
That's what `OXAIGEN_AUTH_DEV_MODE` is for.

### Terminal 1 — start the mock server

```bash
poetry run oxaigen-auth-mock
```

This boots the mock on `http://127.0.0.1:8765` and serves the full `/_oxa_auth/*` surface (login, callback,
token-exchange, get-me, test-app-permission, etc.). Leave this running.

### Terminal 2 — run your backend with the SDK in dev mode

Set `OXAIGEN_AUTH_DEV_MODE=1` so the SDK routes validation calls to the mock instead of deriving the URL from the
incoming request's `Host`:

```bash
export OXAIGEN_AUTH_DEV_MODE=1
# Optional — only set if your mock isn't on the default 127.0.0.1:8765
# export OXAIGEN_AUTH_DEV_PROXY_URL=http://127.0.0.1:8765

poetry run uvicorn my_app.main:app --reload --port 8000
```

Your backend now runs on `http://127.0.0.1:8000` and every SDK validation call (`/_oxa_auth/get-me`,
`/_oxa_auth/test-app-permission/...`) goes to the mock on `127.0.0.1:8765`.

### How the SDK picks the auth URL

The SDK's `derive_proxy_base_url` resolves in this order:

1. **`OXAIGEN_AUTH_PROXY_URL_OVERRIDE`** — if set, used verbatim. Wins over everything, including dev mode. Useful for
   pointing at a fixed staging proxy.
2. **`OXAIGEN_AUTH_DEV_MODE=1`** — uses `OXAIGEN_AUTH_DEV_PROXY_URL` (default `http://127.0.0.1:8765`). This is the
   normal local-dev path.
3. **Default (production)** — builds `{scheme}://{host}` from the incoming request's `X-Forwarded-Host` or `Host`
   header, assuming the app is behind the Oxaigen proxy.

So in dev you generally only need `OXAIGEN_AUTH_DEV_MODE=1`; the default `OXAIGEN_AUTH_DEV_PROXY_URL` already matches
the mock's default bind address.

### Sending requests against your dev backend

With the mock running, the default token `mock-dev-token` is valid. You can hit your backend directly:

```bash
curl -H "Authorization: Bearer mock-dev-token" http://127.0.0.1:8000/v1/me
```

The SDK will pull the bearer token off the header, call the mock at `http://127.0.0.1:8765/_oxa_auth/get-me`, and
resolve a `User`. If you're driving a frontend against the mock, the access cookie set by the mock's callback flow will
also be accepted by the SDK on same-origin requests.

### Tips

- If you change mock env vars (e.g. `OXAIGEN_AUTH_MOCK_PERMISSIONS`), restart Terminal 1 — the mock reads env at
  startup.
- Token validation results are cached for 30s by default. If you flip a token between valid and invalid while testing,
  either wait out the TTL or restart your backend.
- Running the mock on a non-default host/port? Set `OXAIGEN_AUTH_DEV_PROXY_URL` in Terminal 2 to match (e.g.
  `http://127.0.0.1:9000`), and also set `OXAIGEN_AUTH_MOCK_HOST`/`OXAIGEN_AUTH_MOCK_PORT` in Terminal 1.
- To temporarily point dev traffic at a remote staging proxy instead of the local mock, unset `OXAIGEN_AUTH_DEV_MODE`
  and set `OXAIGEN_AUTH_PROXY_URL_OVERRIDE=https://your-staging-host` — no other changes needed.
