Skip to main content
Meerkat’s auth story has two layers. The fast path is environment variables (RKAT_ANTHROPIC_API_KEY, RKAT_OPENAI_API_KEY, RKAT_GEMINI_API_KEY, or the matching provider-native names) - set them and rkat run works. The powerful path is realm-scoped bindings: declare a realm in your config, register global credentials via rkat auth login or binding-scoped credentials through REST/RPC, and pass --auth-binding <realm>:<binding> for a session or structural auth_binding on a mob-member spawn.
rkat auth login provisions the reserved global realm in the home-rooted doc at ~/.rkat/config.toml. Once configured, global is the implicit tail of a workspace realm’s chain, so 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 must explicitly target the realm that defines the binding. A child-addressed inherited write is rejected with the owner identity. 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. The environment fallback is a synthesized binding with a typed origin = "synthetic_env_default" and CredentialSourceSpec::Env. It is ephemeral, is not a durable realm, and cannot participate in inheritance.

The fast path: env vars

That’s it. Meerkat synthesizes an ephemeral binding that reads the key, then uses the normal backend/auth compatibility and provider-resolution path. RKAT_* variants take precedence over 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. Azure image generation can additionally use RKAT_AZURE_OPENAI_IMAGE_GENERATION_DEPLOYMENT and RKAT_AZURE_OPENAI_IMAGE_GENERATION_API_VERSION (or their unprefixed equivalents).
openai_api with an API key and chatgpt_backend with ChatGPT OAuth are different account surfaces. The first uses an OpenAI API organization/project; the second uses ChatGPT/Codex subscription and workspace entitlement. A model listed by rkat models is supported by Meerkat but may still be unavailable to the active account. Keep the backend, auth method, and binding default_model aligned.

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

Interactive rkat auth login openai provisions the ChatGPT OAuth backend. The non-interactive API-key example provisions the public OpenAI API backend. They can expose different model sets because they use different account systems. GitHub Copilot uses device authorization and provisions three routes over one shared credential account:
The generated routes are global:copilot_openai, global:copilot_anthropic, and global:copilot_gemini. They remain ordinary OpenAI, Anthropic, and Gemini provider identities; credential_account = "github_copilot" only makes their GitHub token, derived Copilot token cache, AuthMachine lifecycle, status, and logout shared. Meerkat queries the signed-in account’s /models endpoint and rejects a known-unavailable model/dialect before dispatch. If discovery is temporarily unavailable, auth still succeeds and the request is attempted normally. A known-unavailable error includes the compatible model IDs observed for that account; IDs outside Meerkat’s built-in catalog also require a normal [models.<id>] configuration entry.
GitHub does not currently publish a stable third-party Copilot inference contract. This native integration uses the observed GitHub device client identity and CAPI headers provisionally, without linking the Copilot SDK or CLI. GitHub may change or restrict that contract. Use it only with an account entitled to GitHub Copilot and review GitHub’s current terms before deployment.
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 and use an external source such as the env profiles above, or complete login through REST/RPC with explicit realm_id, binding_id, and profile_id. CLI login never populates a prod managed-store profile; credential writes are strict-owner.

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 managed_store or env. The stock CLI uses its file-backed TokenStore profile (mode 0600 on Unix). Native hosts can enable native-keyring and deliberately select the keyring-aware profile.
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 Between Turns

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 on an RPC turn/start request 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. It does not silently replace an explicit configured binding with the environment fallback.

Per-mob-member override

The mob runtime does not promote ambient credentials into member authority. Live model-backed spawn paths that require credentials must receive an explicit binding, either from the compiled member declaration/overlay or as auth_binding on the host-side spawn request:
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 shipped rkat CLI enables Anthropic, OpenAI, Gemini, OAuth, native-keyring, and refresh-file locking in its dependency graph, while its stock TokenStore profile remains file-backed. Bedrock, Vertex, and Foundry transport features live on the corresponding provider crates and must be selected deliberately in a custom build.

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