Skip to main content
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.
rkat auth login provisions the reserved global realm in the home-rooted doc at ~/.rkat/config.toml. Because global is the universal tail of every realm chain, a single sign-in is inherited by every workspace realm (credential reads inherit down the chain). For a binding owned by a specific realm such as prod:openai, manage that realm’s config entries directly; credential writes are strict-owner and land only in the realm that defines the binding. See Realm inheritance.

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

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. 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

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

Log in

rkat auth login persists credentials and the matching [realm.global] binding section into the home-rooted global realm doc (~/.rkat/config.toml), so the sign-in is inherited by every workspace realm via the chain tail. To use a binding owned by another realm (for example prod:openai), declare that binding in the owning realm’s config; credential writes are strict-owner and land only in the realm that defines the binding.

Use the binding

Subcommands

1

Login and profile management

2

Status and refresh

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.
3

Delete

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. 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.
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.
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.

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.
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:
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.

Native desktop hosts

Native applications should use HostAuthService for the complete interactive transaction instead of recreating the CLI or RPC flow. The service owns strict binding-owner resolution, PKCE and one-time state, exchange, coordinated TokenStore persistence, AuthMachine lifecycle publication, rollback, status, and logout. The application owns only its loopback listener, browser launch, and UI. Open one persistence capability and share that exact capability between AgentFactory and HostAuthService:
Keep the MeerkatMachine alive for as long as the factory and auth service; the snippet returns it in Arc, while a real host normally stores all three in one application state object. A TokenStore open failure is returned as a fault and is never collapsed into interactive_login_required. Managed-store resolution also requires per-session AuthMachine bindings. Do not use the factory’s standalone build mode for a runtime-backed desktop session:
For a keyring-preferred desktop, enable Meerkat’s native-keyring feature and replace default_auto() with default_keyring_auto(). That profile preserves an existing CLI file credential as the per-key authority, but a brand-new key is written to the OS keyring. Use the same keyring-aware backend in every process that must see desktop-first credentials; the stock CLI intentionally uses the file profile to avoid surprise OS keychain prompts. The browser flow is split for native UI:
  1. Bind the loopback listener and choose its redirect URI.
  2. Call login_start(config, target, redirect_uri), then open the returned authorize_url.
  3. Pass the callback’s state and code to login_complete.
  4. Use status and logout for secret-free UI projections and sign-out.

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 the refresh window (AUTH_LEASE_TTL_REFRESH_WINDOW_SECS, 60 seconds before expiry), 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: 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 — observe auth events or gate turns on credential state
  • Mobs — per-member auth_binding overrides
  • Machine Authority — rationale behind single-owner runtime state