# auth.md

You are an agent. Barter Funds supports **agentic registration**: discover → register → (claim if needed) → exchange for an access_token → call API → handle revocation. Follow the steps in order.

Resource server: `https://app.barterfunds.com`
Authorization server: `https://app.barterfunds.com`

---

## Step 1 — Discover

On a 401, read the `WWW-Authenticate` header:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://app.barterfunds.com/.well-known/oauth-protected-resource"
```

### 1a. Fetch the Protected Resource Metadata

```http
GET /.well-known/oauth-protected-resource
Host: app.barterfunds.com
```

Response:

```json
{
  "resource": "https://app.barterfunds.com/",
  "resource_name": "Barter Funds",
  "resource_logo_uri": "https://app.barterfunds.com/logo.png",
  "authorization_servers": ["https://app.barterfunds.com/"],
  "scopes_supported": ["account.read", "transactions.read", "wallet.read"],
  "bearer_methods_supported": ["header"]
}
```

### 1b. Fetch the Authorization Server Metadata

```http
GET /.well-known/oauth-authorization-server
Host: app.barterfunds.com
```

Response (see `/.well-known/oauth-authorization-server` for the live document). Key fields:

- `issuer` — `https://app.barterfunds.com`
- `token_endpoint` — `https://app.barterfunds.com/oauth2/token`
- `revocation_endpoint` — `https://app.barterfunds.com/oauth2/revoke`
- `agent_auth.identity_endpoint` — `https://app.barterfunds.com/agent/identity`
- `agent_auth.claim_endpoint` — `https://app.barterfunds.com/agent/identity/claim`

---

## Step 2 — Pick a method

| What you have | Method |
|---|---|
| A session exchangeable for an ID-JAG bound to `https://app.barterfunds.com/` | `identity_assertion` |
| Only the user's email | `service_auth` (claim ceremony required) |
| Neither | `anonymous` (claim ceremony optional) |

For `identity_assertion`, verify your assertion type is in `agent_auth.identity_assertion.assertion_types_supported` before sending.

---

## Step 3 — Register

Before sending any registration, surface `resource_name` ("Barter Funds") and the scopes you'll act under to the user for consent. Skip for `anonymous`.

### identity_assertion + id-jag

Mint an ID-JAG with `aud = "https://app.barterfunds.com/"`, a fresh `jti`, near-term `exp` (~5 min), `email_verified: true`, and `auth_time`.

```http
POST /agent/identity
Host: app.barterfunds.com
Content-Type: application/json

{
  "type": "identity_assertion",
  "assertion_type": "urn:ietf:params:oauth:token-type:id-jag",
  "assertion": "<id-jag-jwt>"
}
```

Success response:

```json
{
  "registration_id": "reg_...",
  "registration_type": "identity_assertion",
  "identity_assertion": "<service-signed-assertion>",
  "assertion_expires": "2026-06-19T14:00:00.000Z",
  "scopes": ["account.read", "transactions.read", "wallet.read"]
}
```

→ Proceed to Step 5.

### service_auth (verified email)

```http
POST /agent/identity
Host: app.barterfunds.com
Content-Type: application/json

{
  "type": "service_auth",
  "login_hint": "user@example.com"
}
```

Response includes `claim_token` and a `claim` block (see Step 4).

### anonymous

```http
POST /agent/identity
Host: app.barterfunds.com
Content-Type: application/json

{
  "type": "anonymous"
}
```

Response includes a pre-claim `identity_assertion` scoped to `pre_claim_scopes` and a `claim_token` for later ownership.

---

## Step 4 — Claim ceremony (service_auth / anonymous)

### 4a. Get ceremony materials

For `anonymous`, POST to `/agent/identity/claim` with the `claim_token` and user email to get a `claim_attempt` block.

### 4b. Hand off to the user

Surface `verification_uri` and `user_code` to the user. The user opens the link, signs in, and types the code. **The service never emails the code.**

### 4c. Poll for completion

```http
POST /oauth2/token
Host: app.barterfunds.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:workos:agent-auth:grant-type:claim&claim_token=<token>
```

Returns `authorization_pending` while waiting; on success returns a standard token response plus an `identity_assertion`.

---

## Step 5 — Exchange the assertion

```http
POST /oauth2/token
Host: app.barterfunds.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=<identity_assertion>
&resource=https://app.barterfunds.com/
```

Response:

```json
{
  "access_token": "<token>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "account.read transactions.read wallet.read"
}
```

No `refresh_token` is issued. Re-exchange the same `identity_assertion` when the `access_token` expires.

---

## Step 6 — Use the access_token

```http
Authorization: Bearer <access_token>
```

When `access_token` expires → re-exchange `identity_assertion`.
When `identity_assertion` expires or `/oauth2/token` returns `invalid_grant` → restart at Step 3.

---

## Step 7 — Revocation

**Credential layer (RFC 7009)** — agent-callable:

```http
POST /oauth2/revoke
Host: app.barterfunds.com
Content-Type: application/x-www-form-urlencoded

token=<access_token>
```

The `identity_assertion` survives; re-exchange for a fresh `access_token`.

**Registration layer (RFC 8935 SET delivery)** — provider-driven via `events_endpoint`. The provider POSTs a `secevent+jwt` invalidating the assertion and all derived tokens. You discover this as `invalid_grant` on the next exchange → restart at Step 3.

---

## Errors

| Code | Endpoint | Action |
|---|---|---|
| `invalid_issuer` | `/agent/identity` | Provider not trusted; fall back to `service_auth` |
| `invalid_signature` | `/agent/identity` | Recheck ID-JAG signing |
| `expired` | `/agent/identity` | Mint a fresh ID-JAG |
| `replay_detected` | `/agent/identity` | Use a new `jti` |
| `invalid_audience` | `/agent/identity` | Set `aud` to `https://app.barterfunds.com/` |
| `interaction_required` | `/agent/identity` | Proceed to claim ceremony (Step 4) |
| `login_required` | `/agent/identity` | `auth_time` too stale; re-authenticate user |
| `identity_assertion_not_enabled` | `/agent/identity` | Use `service_auth` or `anonymous` |
| `service_auth_not_enabled` | `/agent/identity` | Use `identity_assertion` or `anonymous` |
| `anonymous_not_enabled` | `/agent/identity` | Use a method with user identity |
| `invalid_claim_token` | `/agent/identity/claim` | Re-register to get a fresh token |
| `claim_expired` | `/agent/identity/claim` | Re-call `/agent/identity/claim` |
| `authorization_pending` | `/oauth2/token` | Continue polling |
| `expired_token` | `/oauth2/token` | Re-call `/agent/identity/claim` |
| `invalid_grant` | `/oauth2/token` | Restart at Step 3 |
