> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rkat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Auth

> Realm-scoped credential resolution: from the env-var happy path to OAuth, cloud IAM, and per-member overrides

Meerkat's auth story has two layers. The **fast path** is env vars
(`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, or
`AZURE_OPENAI_API_KEY` plus `AZURE_OPENAI_ENDPOINT`) — set them and
`rkat run` works. The **powerful path** is realm-scoped bindings:
declare a realm in your config, register credentials via `rkat auth
login` or the REST/RPC surfaces, and pass `--auth-binding <realm>:<binding>` to scope a session or mob member to that specific
binding.

<Note>
  Today, CLI `rkat auth login` is primarily a convenience bootstrap flow for the default `dev:*` credential namespace. If you need explicit realm/profile-targeted auth persistence such as `prod:openai`, use the REST/RPC auth surfaces or manage the realm config entries directly.
</Note>

## What this guide is for

Use this guide when you need more than the env-var fast path:

* explicit realm-scoped bindings
* OAuth or cloud-IAM-backed auth
* hot-swapping provider identity
* per-mob-member credential overrides

Both paths route through the same `ProviderRuntimeRegistry`. There's no
hidden magic path: the env fallback is a synthesized realm with
`CredentialSourceSpec::Env`, resolved by the same code that handles
your configured realms.

## The fast path: env vars

```bash theme={null}
export ANTHROPIC_API_KEY=sk-ant-...
rkat run "What's the weather like in 2026?"
```

That's it. If the env var is set, Meerkat synthesizes a default realm
(`env_default`) with a binding that reads the key via
`CredentialSourceSpec::Env`, then resolves normally. `RKAT_*`-prefixed
env vars (e.g., `RKAT_ANTHROPIC_API_KEY`) take precedence over the
provider-native names.

| Provider        | Primary env var                                     | Fallback                                                                                         |
| --------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| Anthropic       | `ANTHROPIC_API_KEY`                                 | —                                                                                                |
| OpenAI          | `OPENAI_API_KEY`                                    | —                                                                                                |
| OpenAI on Azure | `AZURE_OPENAI_API_KEY` plus `AZURE_OPENAI_ENDPOINT` | Optional `AZURE_OPENAI_IMAGE_GENERATION_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_GENERATION_API_VERSION` |
| Gemini          | `GEMINI_API_KEY`                                    | `GOOGLE_API_KEY`                                                                                 |

If both public OpenAI and unprefixed Azure OpenAI env vars are present, the
public OpenAI env default wins. Use `RKAT_AZURE_OPENAI_API_KEY` plus
`RKAT_AZURE_OPENAI_ENDPOINT`, or an explicit realm binding, to make Azure
OpenAI the env default in that case.

## The powerful path: realms + bindings

A **realm** is a named collection of backend profiles, auth profiles,
and provider bindings. It lets you:

* Run multiple sessions against different accounts or tenants
* Mix OAuth (Claude.ai Pro/Max), api\_key (keyed by CI), and cloud IAM
  (Bedrock/Vertex/Foundry) in one process
* Hot-swap a session's active binding mid-conversation
* Give different mob members different credentials

### Realm shape

```toml theme={null}
# ~/.rkat/config.toml (or project-local .rkat/config.toml)

[realm.prod.backend.anthropic_primary]
provider = "anthropic"
backend_kind = "anthropic_api"

[realm.prod.backend.openai_primary]
provider = "openai"
backend_kind = "openai_api"

[realm.prod.auth.anthropic_oauth]
provider = "anthropic"
auth_method = "claude_ai_oauth"
source = { kind = "managed_store" }

[realm.prod.auth.openai_apikey]
provider = "openai"
auth_method = "api_key"
source = { kind = "managed_store" }

[realm.prod.binding.default]
backend_profile = "anthropic_primary"
auth_profile    = "anthropic_oauth"

[realm.prod.binding.openai]
backend_profile = "openai_primary"
auth_profile    = "openai_apikey"
```

Azure OpenAI uses the same provider id (`openai`) with an Azure-specific
backend/auth pair:

```toml theme={null}
[realm.prod.backend.azure_openai]
provider = "openai"
backend_kind = "azure_openai"
base_url = "https://my-resource.cognitiveservices.azure.com"
options = { image_generation_deployment = "gpt-image-2", image_generation_api_version = "preview" }

[realm.prod.auth.azure_openai_key]
provider = "openai"
auth_method = "azure_api_key"
source = { kind = "managed_store" }

[realm.prod.binding.azure_openai]
backend_profile = "azure_openai"
auth_profile    = "azure_openai_key"
default_model   = "gpt-5.5" # Azure deployment name
```

### Log in

```bash theme={null}
# Interactive OAuth (opens a browser; stores refresh token in the
# local TokenStore credentials file, 0600 on Unix).
rkat auth login anthropic

# Scripted (api_key):
rkat auth login openai --non-interactive --secret "$OPENAI_KEY"
```

The current CLI login flow is convenient for local setup, but it does not yet let you choose an arbitrary target realm/profile name at login time. It seeds the runtime-managed default `dev:*` identities and is best thought of as the easiest way to get persisted credentials into Meerkat quickly.

### Use the binding

```bash theme={null}
rkat run --auth-binding prod:default "hello"
rkat run --auth-binding prod:openai   "hello"
```

## Subcommands

<Steps>
  <Step title="Login and profile management">
    ```bash theme={null}
    rkat auth realms                       # list realms defined in config
    rkat auth profiles --realm prod        # list profiles/backends/bindings
    rkat auth profile --realm prod <id>    # inspect one profile
    rkat auth login <provider>             # start OAuth or api_key flow
    rkat auth logout <profile_id>          # clear persisted credentials
    ```
  </Step>

  <Step title="Status and refresh">
    ```bash theme={null}
    rkat auth status --realm prod <profile_id>    # TokenStore state + expiry
    rkat auth test --realm prod <binding_id>      # dry-run the binding
    rkat auth refresh --realm prod <profile_id>   # force-refresh OAuth tokens
    ```

    `rkat auth refresh` is a no-op for `api_key` / `azure_api_key` / `static_bearer`
    auth methods (nothing to refresh). For OAuth-backed methods it
    exchanges the persisted refresh token for a fresh access token and
    writes the new bundle back to the TokenStore.
  </Step>

  <Step title="Delete">
    ```bash theme={null}
    rkat auth profile-delete --realm prod <profile_id> -y
    ```
  </Step>
</Steps>

## Release auth smoke

`make e2e-auth` is the CI-safe auth smoke lane. It drives Meerkat's
browser OAuth plumbing against a local OAuth fixture, verifies token-store
and AuthMachine lifecycle behavior across login, status, refresh, restart,
resolve, and logout, and skips optional live-provider canaries when their
seeded credentials are absent.

Real third-party browser login pages remain a manual pre-release check.
Do not automate provider-hosted OpenAI, Anthropic, or Google login pages in
CI; those pages are intentionally subject to MFA, CAPTCHA, account policy,
and provider UI changes.

Manual browser-login release checklist:

* Start login for the target provider.
* Confirm the system browser opens the provider-hosted login or consent page.
* Complete the callback and confirm Meerkat reports the login complete.
* Check auth status and confirm the target binding is `valid`.
* Run one tiny call through that binding.
* Logout and confirm auth status is cleared and the credential is gone.

## Auth methods

Each provider's runtime accepts a matrix of `(backend_kind,
auth_method)` pairs. The table below summarizes what's wired today.

| Provider  | backend\_kind        | auth\_method                                       | Notes                                                                                       |
| --------- | -------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Anthropic | `anthropic_api`      | `api_key`, `static_bearer`                         | Direct to api.anthropic.com                                                                 |
| Anthropic | `anthropic_api`      | `claude_ai_oauth`                                  | Claude Pro/Max OAuth (PKCE, S256)                                                           |
| Anthropic | `anthropic_api`      | `oauth_to_api_key`                                 | Console OAuth → provisions api\_key                                                         |
| Anthropic | `anthropic_api`      | `external_authorizer`                              | Host-resolved auth injection                                                                |
| Anthropic | `bedrock`            | `bedrock_bearer`, `bedrock_aws_sigv4`              | AWS SigV4 / env-bearer                                                                      |
| Anthropic | `vertex`             | `vertex_google_auth`                               | GoogleAuth / Vertex AI                                                                      |
| Anthropic | `foundry`            | `foundry_api_key`, `foundry_azure_ad`              | Azure AI Foundry                                                                            |
| OpenAI    | `openai_api`         | `api_key`, `static_bearer`, `external_authorizer`  | Direct to api.openai.com or host-resolved auth                                              |
| OpenAI    | `azure_openai`       | `azure_api_key`                                    | Azure OpenAI Responses API; `base_url` required; session model is the Azure deployment name |
| OpenAI    | `chatgpt_backend`    | `managed_chatgpt_oauth`                            | ChatGPT Plus/Pro OAuth                                                                      |
| OpenAI    | `chatgpt_backend`    | `external_chatgpt_tokens`                          | Host-supplied tokens                                                                        |
| Google    | `google_genai`       | `api_key`, `bearer_api_key`, `external_authorizer` | Direct to Google GenAI or host-resolved auth                                                |
| Google    | `vertex_ai`          | `api_key_express`                                  | Vertex API Express key                                                                      |
| Google    | `vertex_ai`          | `adc`, `compute_adc`                               | ADC / compute metadata                                                                      |
| Google    | `google_code_assist` | `google_oauth`                                     | Gemini Code Assist OAuth                                                                    |

External commands (`CredentialSourceSpec::Command`) and file
descriptors (`CredentialSourceSpec::FileDescriptor`) let host
applications inject tokens without going through OAuth.

## Credential sources

The `source` field on an auth profile declares *where* credentials
come from.

| Kind                                    | Description                                                                                                                           |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `InlineSecret { secret }`               | Secret stored inline in the TOML (use only for dev/local)                                                                             |
| `Env { env, fallback }`                 | Read env var at resolve time via `ResolverEnvironment::env_lookup`                                                                    |
| `ManagedStore { profile }`              | Resolve via the `TokenStore`; in current provider runtimes, persisted lookup is tied to the active auth-profile identity/binding path |
| `PlatformDefault`                       | Use the host platform's default credential chain where supported                                                                      |
| `ExternalResolver { handle }`           | Host-registered JS callback (WASM) or Rust resolver (embedded)                                                                        |
| `Command { program, args, ... }`        | Run an external binary; cache stdout for `refresh_interval_ms`                                                                        |
| `FileDescriptor { fd, scope_override }` | Read credentials from a host-supplied file descriptor                                                                                 |

<Warning>
  Storing api keys inline in config files works but shouldn't be
  checked into git. Prefer `ManagedStore` (which writes to the OS
  keychain or a 0o600 file) or `Env`.
</Warning>

<Note>
  `FileDescriptor` is a host-integration seam rather than a plain standalone resolver path. It requires a host-scoped reader/injection path, so treat it as an advanced embedding surface rather than a generic CLI config trick.
</Note>

## Hot-swap mid-session

When a session is created with `--auth-binding prod:default`, its
persisted `SessionMetadata.auth_binding` carries that binding through
resume and hot-swap. Changing the model/provider mid-turn via the RPC
`turn/start` override keeps the binding by default; pass an explicit
`auth_binding` on the turn request to scope the swap to a different
realm or binding.

```json theme={null}
// RPC turn/start — hot-swap to openai binding in the same realm
{
  "method": "turn/start",
  "params": {
    "session_id": "...",
    "prompt": "...",
    "model": "gpt-5.5",
    "auth_binding": {"realm": "prod", "binding": "openai"}
  }
}
```

The factory re-enters `ProviderRuntimeRegistry::resolve` with the new
binding — no env-default fallback, no cross-realm credential bleed.

## Per-mob-member override

Mob members default to env-default / config-realm fallback credentials.
Pass `auth_binding` on the host-side mob spawn surface to scope a member to a
specific binding:

```json theme={null}
{
  "method": "mob/spawn",
  "params": {
    "mob_id": "research",
    "profile": "analyst",
    "agent_identity": "a1",
    "auth_binding": {"realm": "prod", "binding": "openai"}
  }
}
```

Members in the same mob can use different providers or tenants
without cross-contamination. Public wire surfaces accept the structural
`auth_binding` object only; the colon-joined `realm:binding[:profile]`
form is a CLI input convenience and is converted before it crosses the
wire boundary.

## Refresh coordination

Refresh semantics are owned by the `AuthMachine` DSL (one instance
per `<realm>:<binding>`). On resolve, the runtime:

1. Checks `expires_at`. If present and within 80% of TTL, marks the
   lease `Expiring` via the DSL.
2. Under `Expiring`, the next resolve triggers `BeginRefresh`.
   In-process dedup ensures only one refresh HTTP call per binding
   even under concurrent resolves; cross-process dedup via filesystem
   lockfile when `refresh-file-lock` is enabled.
3. On successful refresh, fires `CompleteRefresh` and updates
   `expires_at`. On failure, `RefreshFailedTransient` (retry)
   or `RefreshFailedPermanent` (routes to `ReauthRequired` +
   emits an `[AUTH_REAUTH_REQUIRED]` system notice on the session).

The lifecycle is TLC-verified in `specs/machines/auth/`.

## Audit logging

Every accepted lifecycle transition emits a structured tracing event:

* `target = "meerkat::auth::audit"`
* Fields: `binding_key`, `action`, `from_phase`, `to_phase`

REST and RPC surfaces also emit audit events for user-initiated
actions: `create_profile`, `delete_profile`, `login_oauth_complete`,
`login_device_complete`, `logout`. Filter any `tracing::Subscriber`
on the `meerkat::auth::audit` target to build a persistent audit log.

## Feature flags

The `meerkat-providers` crate gates optional auth methods behind
features so you only pay for what you use:

| Feature             | Enables                                       |
| ------------------- | --------------------------------------------- |
| `oauth`             | OAuth flows (Claude.ai, ChatGPT, Code Assist) |
| `native-keyring`    | OS keychain storage for tokens                |
| `refresh-file-lock` | Cross-process refresh dedup                   |
| `aws-auth`          | Bedrock backend + SigV4 signing               |
| `google-oauth`      | Vertex + Code Assist + ADC                    |
| `azure-auth`        | Foundry backend + Azure AD                    |

The `rkat` CLI enables `anthropic`, `openai`, `gemini`, and `oauth`
by default; add cloud-specific features at compile time for
Bedrock/Vertex/Foundry.

## See also

* [Hooks](/guides/hooks) — observe auth events or gate turns on
  credential state
* [Mobs](/guides/mobs) — per-member `auth_binding` overrides
* [Machine Authority](/reference/machine-authority) — rationale behind single-owner runtime state
