From fde10e818c2adcb204fcdfa213fd53cc5427210c Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Tue, 21 Jul 2026 11:32:55 -0600 Subject: [PATCH] docs: Improved openAI compatible provider OAuth documentation --- Clients.md | 248 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 192 insertions(+), 56 deletions(-) diff --git a/Clients.md b/Clients.md index 4b8eaed..1b0142f 100644 --- a/Clients.md +++ b/Clients.md @@ -210,20 +210,165 @@ clients: auth: oauth ``` -### OAuth for OpenAI-Compatible Providers +## OAuth for OpenAI-Compatible Providers -For any provider that speaks the OpenAI API shape, you can enable OAuth on an `openai-compatible` client. Coyote resolves -OAuth configuration in two layers: +For any provider that speaks the OpenAI API shape (`type: openai-compatible`), you can enable OAuth by setting +`auth: oauth` and configuring an `oauth:` block. This section is your reference for what fields each OAuth flow needs. -1. **Bundled defaults** in `models.yaml` under `provider: ` (Coyote ships defaults for providers like xAI). -2. **Inline overrides** in your `config.yaml` under `clients[i].oauth`. You can either supply the whole block (for - providers not shipped with Coyote) or override individual fields (e.g. to point at a corporate token endpoint). +**Not needed for built-in providers** (Claude, Gemini, OpenAI, xAI-when-using-bundled-defaults): those work with just +`auth: oauth`, meaning no fields are required. See the [provider-specific notes above](#openai-oauth-note) for details. -For each optional field, the user's `config.yaml` value wins over the `models.yaml` default. If neither source provides -a required field (`client_id`, `token_url`), Coyote will bail with a clear error when you attempt to authenticate. +### Which flow does my provider use? -**Example: inline OAuth for a private gateway:** +Look at your provider's own login docs or CLI for signal words: +| What your provider does | Flow to configure | Section | +|---|---|---| +| Opens your browser, redirects to a `http://localhost` or `http://127.0.0.1` URL | Authorization Code + PKCE (loopback callback) | [Flow 1, Mode A](#flow-1-authorization-code--pkce) | +| Opens your browser, redirects to a `https://...` URL you'd copy from the address bar | Authorization Code + PKCE (browser-paste) | [Flow 1, Mode B](#flow-1-authorization-code--pkce) | +| Displays a short user code you enter at a URL, with no browser callback | Device Authorization Grant (RFC 8628) | [Flow 2](#flow-2-device-authorization-grant-rfc-8628) | +| No user at all; just `client_id` + `client_secret` exchanged headlessly | Client Credentials (RFC 6749 ยง4.4) | [Flow 3](#flow-3-client-credentials-m2m) | + +If your provider's OAuth app is already registered with a specific redirect URI, Coyote's routing follows it: put the +value in `redirect_uri` and Coyote does the right thing based on the URL shape (loopback vs public). + +### Flow 1: Authorization Code + PKCE + +Standard OAuth 2.0 with PKCE (RFC 7636). Coyote opens the authorize URL in your browser, receives back an authorization +code, and exchanges it for tokens. Refresh tokens are supported (silently refreshed on expiry). + +Coyote picks between two delivery modes automatically based on what you set for the callback: + +#### Mode A: Loopback callback (Coyote listens on your machine) + +Coyote binds a TCP listener on 127.0.0.1 and the browser redirects there directly. This is the smoothest UX (i.e. no manual +copy/paste), but requires that the provider's OAuth app allows a loopback redirect URI. + +**Required fields:** +- `flow: pkce` (or omit. This is the default) +- `client_id`: your OAuth app's client ID +- `authorize_url`: where the browser goes to authorize +- `token_url`: where Coyote exchanges the code for tokens + +**Callback config (pick one):** +- `redirect_port: `: if the provider registered a specific port (e.g. `56121` for xAI). Coyote synthesizes + `http://127.0.0.1:/callback`. +- `redirect_uri: http://127.0.0.1:/`: full custom loopback URL (overrides `redirect_port`). +- Omit both: Coyote picks an ephemeral port on 127.0.0.1 (only works if the provider allows arbitrary loopback ports). + +**Optional fields:** +- `scopes: [scope1, scope2]`: space-joined into the `scope=` query param. +- `extra_authorize_params: { key: value }`: extra query params on the authorize URL (e.g. xAI needs `plan: generic`). +- `echo_pkce_in_token_exchange: true`: some providers (like xAI) require re-sending `code_challenge` on token exchange. +- `include_state_in_token_exchange: false`: some providers reject `state` on the token endpoint (default `true`). +- `token_request_format: form_url_encoded`: some providers require form-encoded token bodies (default is + `form_url_encoded` for openai-compatible). +- `client_secret`: rare with PKCE, but supported for hybrid setups. + +**Example:** +```yaml +clients: + - type: openai-compatible + name: my-provider + api_base: https://api.provider.com/v1 + auth: oauth + oauth: + client_id: '{{PROVIDER_CLIENT_ID}}' + authorize_url: https://auth.provider.com/oauth/authorize + token_url: https://auth.provider.com/oauth/token + redirect_port: 8765 + scopes: [read, write] +``` + +#### Mode B: Browser-paste (you paste the callback back into the terminal) + +For providers whose OAuth app is registered with a **public HTTPS URL** (not loopback). The browser redirects to that +URL, the provider's page displays the code (or you copy it from the URL bar), and you paste it back into Coyote. + +Coyote detects Mode B automatically when `redirect_uri` is set to a **non-loopback URL** (anything other than 127.0.0.1, +localhost, or ::1). No opt-in flag needed. + +**Required fields:** same as Mode A above, except the callback config: +- `redirect_uri: https:///`: the exact URL the provider is registered with. + +**What Coyote accepts at the paste prompt:** +1. **Full callback URL from the browser bar** (e.g. `https://provider.example.com/callback?code=abc&state=xyz`). + Coyote parses `code` and `state` from the query string. +2. **`#` fragment** (Anthropic-style; supported for provider pages that display both concatenated). +3. **Bare code** (just the authorization code, no state). Accepted with a warning. CSRF state validation is + skipped. Use this for providers whose callback page shows only the code. + +**Example:** +```yaml +clients: + - type: openai-compatible + name: my-hosted-provider + api_base: https://api.provider.com/v1 + auth: oauth + oauth: + client_id: '{{PROVIDER_CLIENT_ID}}' + authorize_url: https://auth.provider.com/oauth/authorize + token_url: https://auth.provider.com/oauth/token + redirect_uri: https://provider.com/oauth/callback # public URL -> paste mode + scopes: [read] +``` + +### Flow 2: Device Authorization Grant (RFC 8628) + +For CLIs and headless environments: **no browser callback required**. Coyote displays a short `user_code` and a +`verification_uri`. You visit the URL on any device (phone, another laptop, or the same browser Coyote tries to open) +and enter the code to approve. Coyote polls the token endpoint until you approve or the code expires. Refresh tokens +are supported. + +**Required fields:** +- `flow: device_code` +- `client_id`: your OAuth app's client ID +- `device_authorization_url`: where Coyote requests the initial `user_code` +- `token_url`: where Coyote polls for the token + +**Optional fields:** +- `scopes: [scope1, scope2]` +- `use_pkce_in_device_flow: true`: some providers (like MiniMax) require PKCE with device flow. Coyote generates a + fresh `code_verifier` + `code_challenge` (S256) on each authentication. +- `token_request_format: form_url_encoded`: usually needed for device flow (default for openai-compatible). +- `extra_token_headers: { key: value }`: custom headers applied to both device and token endpoints. +- `client_secret`: rare but supported. + +**Example:** +```yaml +clients: + - type: openai-compatible + name: my-device-flow-provider + api_base: https://api.example.com/v1 + auth: oauth + oauth: + client_id: '{{MY_CLIENT_ID}}' + device_authorization_url: https://auth.example.com/oauth/device_authorization + token_url: https://auth.example.com/oauth/token + flow: device_code + scopes: [openid, profile] +``` + +**Sandbox bonus:** when `IS_SANDBOX=1` (set automatically by `coyote --sandbox`), Coyote renders a scannable QR code +in the terminal so you can point your phone camera at the verification URL and complete the flow in seconds. + +### Flow 3: Client Credentials (M2M) + +For headless machine-to-machine authentication. No user, no browser. Coyote POSTs directly to the token endpoint with +`client_id` + `client_secret`. Best for CI, servers, agent runtimes. + +**Required fields:** +- `flow: client_credentials` +- `client_id` +- `client_secret` +- `token_url` + +**Optional fields:** +- `scopes: [scope1, scope2]` +- `token_request_format` +- `extra_token_headers: { key: value }` + +**Example:** ```yaml clients: - type: openai-compatible @@ -238,49 +383,32 @@ clients: flow: client_credentials ``` -### client_credentials Flow +**Note on refresh:** RFC 6749 forbids refresh tokens for this flow. When the access token expires, Coyote +silently re-runs the client credentials exchange (still no browser or user interaction). -For headless machine-to-machine (M2M) authentication (i.e. no browser, no user interaction), set `flow: client_credentials` -in your `oauth` block. Coyote will `POST` directly to the token endpoint with `client_id` and `client_secret` and cache -the returned access token. +### Providers with Bundled OAuth Defaults -RFC 6749 forbids refresh tokens for the client credentials flow, so when the token expires Coyote silently -re-runs the client credentials exchange (still no browser). This makes it suitable for CI, servers, and other -non-interactive environments. +Coyote ships OAuth defaults for the following providers, so you only need `auth: oauth` in your config, with no fields +required: -### Device Authorization Grant (Device Flow, RFC 8628) +* **xAI:** SuperGrok / X Premium+ subscribers. See [xAI OAuth Note](#xai-oauth-note) above for behavior details. -For CLIs and headless environments; no browser callback required. Coyote displays a short -`user_code` and a `verification_uri`. You visit the URL on any device (your phone, another -computer, or the same browser Coyote tries to open) and enter the code to approve. Coyote -polls the token endpoint until you approve or the code expires. +To bundle defaults for additional providers locally, edit your `models.yaml` (see [Local Models Override](#local-models-override)) +and add an `oauth:` block under the provider entry using the field reference for the appropriate flow above. -To enable, set `flow: device_code` in your `oauth:` block and provide a `device_authorization_url`: +### Configuration Precedence - clients: - - type: openai-compatible - name: my-device-flow-provider - api_base: https://api.example.com/v1 - auth: oauth - oauth: - client_id: '{{MY_CLIENT_ID}}' - device_authorization_url: https://auth.example.com/oauth/device_authorization - token_url: https://auth.example.com/oauth/token - flow: device_code +Coyote resolves OAuth configuration in two layers: -**Optional:** some providers require PKCE with device flow. Set `use_pkce_in_device_flow: true` to opt -in. Coyote generates a fresh `code_verifier` and `code_challenge` on each authentication. +1. **Bundled defaults** in `models.yaml` under `provider: `. +2. **Inline overrides** in your `config.yaml` under `clients[i].oauth`. -Token refresh works the same as the Auth Code + PKCE flow: `refresh_token` is silently -exchanged for a new `access_token` when the current one expires. +Per-field: `config.yaml` wins over `models.yaml`. If neither source provides a required field for your flow (see the +"Required fields" list in each flow section above), Coyote bails with a clear error at authentication time telling you +which field is missing. -**Sandbox / headless environments:** when Coyote runs inside a sandbox (`coyote --sandbox`), -your host OAuth tokens (`~/.cache/coyote/oauth/`) are copied into the container so agents can -call OAuth-authenticated providers without re-authenticating. If you do need to authenticate -from inside a sandbox, Device Flow works out of the box: Coyote prints a QR code you can -scan with your phone camera to visit the verification URL. Auth Code + PKCE flows (like xAI's) -generally cannot complete inside a sandbox because they require an inbound callback listener; -authenticate on the host first and let the token copy carry you through. +You can override individual fields. For example, if the bundled default has the wrong token URL for your corporate +gateway, just set `oauth.token_url` inline and the rest of the bundled defaults still apply. ### Known Limitation: Token Namespacing @@ -288,27 +416,35 @@ OAuth tokens are stored on disk at `~/.cache/coyote/oauth/{client_name}_oauth_to field only (not by provider type). Two clients with the same `name`, even if they use different provider types, will overwrite each other's tokens. -To prevent accidental collisions, Coyote now rejects configuration files with duplicate `name` values across all +To prevent accidental collisions, Coyote rejects configuration files with duplicate `name` values across all `clients[]` entries at load time. Fixing the underlying single-namespace layout is future work. -### OpenAI-Compatible Providers with Bundled OAuth Defaults +### Sandbox and headless environments -Coyote ships OAuth defaults for the following providers, so you only need `auth: oauth` in your config: +When Coyote runs inside a sandbox (`coyote --sandbox`), your host OAuth tokens (`~/.cache/coyote/oauth/`) are copied +into the container so agents can call OAuth-authenticated providers without re-authenticating. If you do need to +authenticate from inside a sandbox: -* **xAI:** SuperGrok / X Premium+ subscribers +- **Flow 2 (Device Grant)** works out of the box. Coyote prints a QR code you can scan with your phone camera. +- **Flow 3 (Client Credentials)** works fine (no browser or callback needed). +- **Flow 1 Mode A (loopback callback)** generally cannot complete because the loopback listener binds inside the + sandbox's network namespace, unreachable from the host browser. +- **Flow 1 Mode B (browser-paste)** works if you can open the authorize URL in a browser somewhere and paste back. -To bundle defaults for additional providers locally, edit your `models.yaml` (see [Local Models Override](#local-models-override)) -and add an `oauth:` block under the provider entry. +Recommendation: authenticate on the host first and let the sandbox token copy carry you through. ## Providers That Support OAuth -* Claude -* Gemini -* OpenAI (requires ChatGPT Plus or Pro subscription) -* xAI (requires SuperGrok or X Premium+ subscription; configured via `type: openai-compatible`) -* Any OpenAI-compatible provider via an inline `oauth:` block (see [OAuth for OpenAI-Compatible Providers](#oauth-for-openai-compatible-providers)) -> Additional providers using Device Authorization Grant (RFC 8628) can be configured inline via -> `flow: device_code` in the `oauth:` block. See [Device Authorization Grant](#device-authorization-grant-device-flow-rfc-8628). +**Built-in providers:** (just set `auth: oauth`, no fields required): +* **Claude:** See [Claude OAuth Note](#oauth-authentication) +* **Gemini:** See [Gemini OAuth Note](#gemini-oauth-note) +* **OpenAI:** See [OpenAI OAuth Note](#openai-oauth-note) (requires ChatGPT Plus or Pro subscription) +* **xAI:** See [xAI OAuth Note](#xai-oauth-note) (requires SuperGrok or X Premium+ subscription; configured via `type: openai-compatible`, uses bundled defaults) + +**Any OpenAI-compatible provider:** Configured inline via an `oauth:` block. Coyote supports all four common flows: +Authorization Code + PKCE (loopback callback OR browser-paste), Device Authorization Grant (RFC 8628), and Client +Credentials. See [OAuth for OpenAI-Compatible Providers](#oauth-for-openai-compatible-providers) for the field reference +for each flow. # Extra Settings Coyote also lets you customize some extra settings for interacting with APIs: