# OAuth Gateway Integration Guide

GrantFlow exposes a self-serve OAuth endpoint (`POST /agents/oauth/token`) for sandbox and
controlled deployments. In production, token issuance should stay at the gateway or IAM layer.
This guide shows how to wire a real OAuth provider in front of GrantFlow using the three most
common options: Auth0, Okta, and Keycloak.

The pattern is the same in all three cases:

```
Agent / Client  →  OAuth Provider  →  Gateway  →  GrantFlow API
```

GrantFlow receives a shared `X-API-Key` from the gateway. The gateway holds the secret;
GrantFlow never sees user tokens directly.

---

## Prerequisites

- GrantFlow running with `GRANTFLOW_API_KEY=<shared-secret>` set
- A reverse proxy or API gateway in front of GrantFlow (Nginx, Kong, AWS API Gateway, Traefik, etc.)
- An OAuth provider tenant / realm (Auth0, Okta, or Keycloak)

---

## Step 1 — Create an OAuth application in your provider

Create a **Machine-to-Machine** (M2M) or **Service Account** application with the
`client_credentials` grant type. Scopes are optional at the provider level; route-level
enforcement happens in the gateway.

| Provider | App type | Grant |
|---|---|---|
| Auth0 | Machine to Machine | `client_credentials` |
| Okta | API Services | `client_credentials` |
| Keycloak | Client (Confidential) | `client_credentials` |

Record: `client_id`, `client_secret`, and the **token endpoint URL**.

---

## Step 2 — Configure the gateway to validate tokens

The gateway must:

1. Accept `Authorization: Bearer <access_token>` from the client
2. Validate the token against the provider's JWKS endpoint
3. Map the validated identity to a GrantFlow role (see `docs/enterprise-access-layer.md`)
4. Inject `X-API-Key: <shared-secret>` toward GrantFlow
5. Strip the inbound `Authorization` header before forwarding

### Auth0

Token endpoint:
```
https://<your-tenant>.auth0.com/oauth/token
```

JWKS endpoint (for gateway validation):
```
https://<your-tenant>.auth0.com/.well-known/jwks.json
```

Issuer to validate: `https://<your-tenant>.auth0.com/`

### Okta

Token endpoint:
```
https://<your-okta-domain>/oauth2/default/v1/token
```

JWKS endpoint:
```
https://<your-okta-domain>/oauth2/default/v1/keys
```

Issuer to validate: `https://<your-okta-domain>/oauth2/default`

### Keycloak

Token endpoint:
```
https://<keycloak-host>/realms/<realm>/protocol/openid-connect/token
```

JWKS endpoint:
```
https://<keycloak-host>/realms/<realm>/protocol/openid-connect/certs
```

Issuer to validate: `https://<keycloak-host>/realms/<realm>`

---

## Step 3 — Nginx example (auth_request pattern)

This is the minimal Nginx config using `auth_request` to validate a Bearer token before
forwarding to GrantFlow. Replace `<...>` with your values.

```nginx
server {
    listen 443 ssl;
    server_name grantflow.example.com;

    # Forward all requests through token validation first
    location / {
        auth_request /auth;
        auth_request_set $auth_status $upstream_status;

        # Inject shared secret; strip inbound Authorization
        proxy_set_header X-API-Key "<shared-grantflow-api-key>";
        proxy_set_header Authorization "";

        proxy_pass http://grantflow-api:8000;
    }

    # Internal token validation endpoint
    location = /auth {
        internal;
        proxy_pass http://token-validator:9000/validate;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
    }
}
```

The `token-validator` is a lightweight sidecar (or a provider SDK call) that:
- Fetches the JWKS from your provider
- Validates the `Authorization: Bearer` token signature and expiry
- Returns `200` on success, `401` on failure

Simple Python example using `python-jose`:

```python
from fastapi import FastAPI, Request, HTTPException
from jose import jwt, jwk
import httpx, os

app = FastAPI()
JWKS_URL = os.environ["JWKS_URL"]
AUDIENCE = os.environ.get("JWT_AUDIENCE", "")
ISSUER = os.environ["JWT_ISSUER"]

@app.get("/validate")
async def validate(request: Request):
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Bearer "):
        raise HTTPException(401)
    token = auth.removeprefix("Bearer ")
    async with httpx.AsyncClient() as client:
        keys = (await client.get(JWKS_URL)).json()
    try:
        jwt.decode(token, keys, algorithms=["RS256"], audience=AUDIENCE, issuer=ISSUER)
    except Exception:
        raise HTTPException(401)
    return {"status": "ok"}
```

---

## Step 4 — Kong example (jwt plugin)

If you use Kong Gateway, the built-in `jwt` plugin handles validation without a sidecar.

```yaml
plugins:
  - name: jwt
    config:
      key_claim_name: iss
      secret_is_base64: false
      uri_param_names: []
  - name: request-transformer
    config:
      add:
        headers:
          - "X-API-Key:<shared-grantflow-api-key>"
      remove:
        headers:
          - Authorization
```

Register each provider's JWKS public key as a Consumer credential in Kong.

---

## Step 5 — Tenant mapping (optional)

If you use GrantFlow's tenant authorization (`GRANTFLOW_TENANT_AUTHZ_ENABLED=true`), the gateway
should also inject `X-Tenant-ID` after mapping the token's `sub`, `org_id`, or a custom claim
to a provisioned tenant.

```nginx
# Example: map a custom claim forwarded by the validator sidecar
auth_request_set $tenant_id $upstream_http_x_tenant_id;
proxy_set_header X-Tenant-ID $tenant_id;
```

Provision tenants in advance via `POST /tenants` before allowing traffic.

---

## Verification

After wiring the gateway:

```bash
# 1. Get a token from your provider
TOKEN=$(curl -s -X POST "$TOKEN_ENDPOINT" \
  -d "grant_type=client_credentials" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  -d "scope=read:all" | jq -r .access_token)

# 2. Call GrantFlow through the gateway
curl -H "Authorization: Bearer $TOKEN" \
  https://grantflow.example.com/health
# Expected: {"status": "ok"}

# 3. Confirm GrantFlow rejects direct access (no shared key)
curl https://grantflow.example.com/generate/preflight \
  -d '{"donor_id":"usaid","country":"Kenya"}' \
  -H "Content-Type: application/json"
# Expected: 401 or 403 (no X-API-Key)
```

---

## What GrantFlow does not do

- Token issuance or OIDC discovery
- User login flows (redirect, PKCE, implicit)
- Role propagation from the token to in-app permissions (see BL-06 in `BACKLOG.md`)

These are gateway/IAM responsibilities. GrantFlow trusts the `X-API-Key` and `X-Tenant-ID`
headers injected by the gateway after the gateway has validated the caller.

---

## Related docs

- `docs/enterprise-access-layer.md` — role model and route family mapping
- `docs/gateway-policy-example.md` — Nginx route-level authorization policy
- `docs/agents/trust-report.md` — what GrantFlow verifies vs. gateway responsibility
- `AGENTS.md` — self-serve OAuth sandbox flow (no gateway required)
