# bOpen.ai agent authentication

bOpen.ai implements the experimental WorkOS auth.md 0.6.0 service-auth flow, reviewed against upstream commit 9d7ffe638694beccc1bc7a5803fecfd45df4f47d. Discover the live endpoints rather than assuming paths.

## Safety boundary

- The agent must never ask for, receive, store, or transmit the user's password.
- The agent must never submit the user code or automate the bOpen claim page.
- The user enters the code only on the bOpen-owned verification page after signing in.
- Anonymous registration and external ID-JAG providers are not enabled.
- This flow is separate from bOpen's RFC 8628 device authorization and Better Auth browser sessions.

## 1. Discover

Fetch https://bopen.ai/.well-known/oauth-protected-resource, select its authorization server, then fetch https://bopen.ai/.well-known/oauth-authorization-server. Validate the exact resource, issuer, endpoint origins, grants, and the agent_auth block.

## 2. Obtain user consent

Before disclosing a login email, show the user the canonical service host (https://bopen.ai), the exact requested scopes, that a new agent registration will be linked to their bOpen account, and that it can be revoked from https://bopen.ai/agent/access. Obtain an affirmative decision.

Supported scopes: profile:read, packs:read, packs:download, chat:invoke, newsletter:subscribe.

## 3. Start service_auth

POST JSON to the discovered identity_endpoint:

```json
{
  "type": "service_auth",
  "login_hint": "user@example.com",
  "scope": "profile:read packs:read"
}
```

`scope` is bOpen's optional narrowing extension. If omitted, only `profile:read` is requested. Never request scopes the user did not approve.

Keep the returned `claim_token` in volatile secret storage. Show the returned `claim.verification_uri` and `claim.user_code` to the user together. Tell the user to open the URL, authenticate directly with bOpen, and enter the code there—not to send the code or a password back to the agent.

## 4. Poll for the user's decision

POST form-encoded data to the discovered token_endpoint:

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

Honor the returned `interval` as a minimum. On `authorization_pending`, wait and retry. On `slow_down`, add at least five seconds and keep the slower interval. Stop on `access_denied` or an expired outer registration. If only the ten-minute code attempt expires, POST the same `claim_token` to the discovered claim_endpoint to obtain a fresh attempt.

On success, store the access token and service-signed identity assertion separately from browser sessions and user credentials. Discard plaintext claim artifacts.

## 5. Refresh and call APIs

Present access tokens only to the exact discovered resource using `Authorization: Bearer <access_token>`. To mint another access token while the service assertion remains valid, POST:

```text
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>&resource=https://bopen.ai
```

The exchange may narrow scope but never widen it. There is no refresh token. Revoke an access token at the discovered revocation_endpoint using RFC 7009 form fields. A user can revoke the whole registration at https://bopen.ai/agent/access.

## Protected resources

- `GET /api/me` — `profile:read`
- `GET /api/me/packs` — `packs:read`
- `GET /api/download/{productId}` — `packs:download`
- `POST /api/agent/newsletter` — `newsletter:subscribe`. The agent may choose a topic, but bOpen derives the recipient from the linked account and sends a human confirmation email. The agent cannot supply another email address or activate delivery.
- Agent chat accepts `chat:invoke` to use the authenticated user's rate-limit tier; it does not grant Sigma publishing credentials.
