# Authentication

OAuth 2.1 for apps that act on a person's behalf, and personal access tokens for tools and scripts. Both act as the person, limited to what they allow.

## Which to use

|  | OAuth 2.1 | Personal access token |
| --- | --- | --- |
| For | Apps people connect: Meta Muse, ChatGPT, Claude, Grok, coding tools that sign in | Your scripts, CI, tools that take a header |
| How | The app sends the person to approve, then gets tokens | The person creates it in Studio and pastes it |
| Token | `cc_oat_...`, valid 1 hour, refreshed automatically | `cc_pat_...`, valid 30 to 365 days or no expiry |
| Limits | Scopes the app asked for, read-only if the person chose it, chosen Concierges | Scopes and Concierges chosen when created |
| Revoke | Developer Access, Connected apps; or `POST /oauth/revoke` | Developer Access, Tokens |

Either way, a token never does more than the person's own role on a Concierge allows, and every call is recorded in the audit log with the app's name.

## OAuth 2.1

Covo follows the MCP authorization specification, so MCP clients discover everything on their own: call the MCP server without a token, read the `WWW-Authenticate` header, and follow it.

| What | Where |
| --- | --- |
| Protected resource metadata (RFC 9728) | `https://covo.lanaai.io/.well-known/oauth-protected-resource/mcp` (MCP) and `https://covo.lanaai.io/.well-known/oauth-protected-resource` (REST) |
| Authorization server metadata (RFC 8414) | `https://covo.lanaai.io/.well-known/oauth-authorization-server` |
| Authorize | `https://covo.lanaai.io/oauth/authorize` |
| Token | `https://covo.lanaai.io/oauth/token` (form or JSON body) |
| Dynamic client registration (RFC 7591) | `https://covo.lanaai.io/oauth/register` |
| Revocation (RFC 7009) | `https://covo.lanaai.io/oauth/revoke` |
| Issuer | `https://covo.lanaai.io` |

### The flow

1. The app calls `https://covo.lanaai.io/mcp` without a token and gets `401` with `WWW-Authenticate: Bearer resource_metadata="https://covo.lanaai.io/.well-known/oauth-protected-resource/mcp"`.
2. It reads that document, then the authorization server metadata it points to.
3. It identifies itself: by a Client ID Metadata Document URL (preferred by ChatGPT and Claude), or by registering at `/oauth/register`.
4. It sends the person to `/oauth/authorize` with `response_type=code`, `client_id`, `redirect_uri`, `state`, `code_challenge` and `code_challenge_method=S256`, and optionally `scope` and `resource`.
5. The person signs in to Covo (if needed) and sees what the app asked for. They can make it read-only and limit it to chosen Concierges, then allow or cancel.
6. Covo redirects to the app with `code`, `state` and `iss` (RFC 9207), or with `error=access_denied`.
7. The app exchanges the code at `/oauth/token` with `grant_type=authorization_code`, `code`, `redirect_uri`, `client_id` and `code_verifier`, and gets an access token (1 hour) and a refresh token (30 days).
8. Before the access token expires, it sends `grant_type=refresh_token`. Each refresh returns a new refresh token; the old one stops working.

### Rules that keep people safe

- PKCE with S256 is required; plain challenges are refused.
- Codes are single use and expire after 10 minutes. A code used twice revokes everything issued with it.
- A refresh token used twice (a sign it was copied) revokes the whole approval; the person connects again.
- Redirect URIs must match what the app registered. https anywhere, http only on `localhost` or `127.0.0.1` (any port, same path), or an app's own scheme for native apps.
- `resource`, when sent, must be this service (its origin, `/mcp` or `/api/v1`).
- Publishing, rolling back and removing things still need `confirm: true` on each call, after the person agrees in the app.

### Scopes

Send any of `agents:read`, `agents:write`, `agents:delete`, `agents:test`, `agents:publish`, `conversations:read` in `scope`, separated by spaces. With no scope, an app asks for `agents:read agents:write`. See [Scopes and roles](https://covo.lanaai.io/docs/scopes).

### Registering a client

```bash
curl -X POST https://covo.lanaai.io/oauth/register \
  -H "Content-Type: application/json" \
  -d '{"client_name":"My agent","redirect_uris":["https://my-agent.example/oauth/callback"]}'
```

The answer has your `client_id`. Public clients (the default, `token_endpoint_auth_method: "none"`) rely on PKCE alone. Ask for `client_secret_post` or `client_secret_basic` to get a secret; it is shown once.

### Exchanging the code

```bash
curl -X POST https://covo.lanaai.io/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code=THE_CODE \
  -d redirect_uri=https://my-agent.example/oauth/callback \
  -d client_id=covo_client_... \
  -d code_verifier=THE_VERIFIER
```

200 response:

```json
{
  "access_token": "cc_oat_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "cc_ort_...",
  "scope": "agents:read agents:write"
}
```

Errors follow RFC 6749: `{"error": "invalid_grant", "error_description": "..."}` with status 400 (401 for `invalid_client`).

## Personal access tokens

- Create them in Studio: Settings, Developer Access, Create a token. They are shown once and stored only as a hash.
- Send them as `Authorization: Bearer cc_pat_...` on every request, REST or MCP.
- Choose a preset or exact scopes, every Concierge or chosen ones, and an expiry. Up to 50 active tokens per account.
- Name the client calling with `X-Client-Name` and `X-Client-Version` (MCP clients send this in `initialize`); it shows in Studio and in the audit log.

> **Important:** Never put a token in a URL, a prompt, a tool description or a log.
