How Authentication Profiles Are Defined and Managed in ClosedClaw’s AuthProfileStore
ClosedClaw stores all external service credentials in a single JSON file called auth‑profiles.json, using the AuthProfileStore schema that supports API keys, static tokens, and OAuth credentials with built‑in versioning, locking, and per‑agent overrides.
The asafelobotomy/closedclaw repository implements a robust authentication management system that centralizes credential storage for external APIs. All authentication profiles are persisted in a versioned JSON format that enables safe concurrent access, legacy migration, and granular per-agent configuration overrides.
AuthProfileStore Schema Definition
The type definitions in src/agents/auth-profiles/types.ts establish the contract for all authentication profiles. The schema supports three distinct credential types wrapped in a discriminated union.
Credential Types
ApiKeyCredential represents simple key-based authentication. It stores a type discriminator set to "api_key", the actual key string, and an optional email field for providers that require email-key pairs.
TokenCredential handles static bearer tokens, including personal access tokens (PATs). It uses type: "token", stores the token value, and optionally tracks an expires timestamp as a Unix epoch number.
OAuthCredential manages full OAuth2 flows with type: "oauth". It stores access and refresh tokens, the expires timestamp, and the associated email or account identifier.
These three types compose the AuthProfileCredential union type, allowing the store to handle heterogeneous authentication methods within a single schema.
Store Structure
The top-level AuthProfileStore interface defines the JSON structure persisted to auth-profiles.json. It contains:
version: The store schema version (controlled byAUTH_STORE_VERSIONinsrc/agents/auth-profiles/constants.ts)profiles: A record mappingprofileIdstrings toAuthProfileCredentialobjectsorder(optional): Per-agent overrides for credential rotation orderlastGood(optional): Tracks the most recently successful profile per providerusageStats(optional): Runtime statistics for round-robin rotation, cooldowns, and failure tracking
Profile IDs follow the convention "<provider>:<name>", with default as the standard name (e.g., anthropic:default).
Managing Authentication Profiles: Load, Merge, and Persist
All high-level operations for managing authentication profiles are implemented in src/agents/auth-profiles/store.ts. These functions handle file I/O, schema validation, concurrent access, and migration.
Loading the Store
loadAuthProfileStore() (lines 195-227) reads the global auth-profiles.json file, coerces raw JSON to the strict AuthProfileStore type, synchronizes external CLI credentials via syncExternalCliCredentials, and falls back to legacy auth.json migration if needed.
loadAuthProfileStoreForAgent(agentDir?) (lines 255-283) provides the same functionality for sub-agent directories. If the sub-agent lacks its own auth-profiles.json, it inherits the main store by creating a copy.
Ensuring and Merging Profiles
ensureAuthProfileStore(agentDir?, options?) (lines 351-366) guarantees a valid AuthProfileStore is returned. For sub-agents, it merges the main store with agent-specific configurations, applying overrides (such as custom order arrays) while preserving global profiles.
Safe Persistence with Locking
updateAuthProfileStoreWithLock({agentDir, updater}) (lines 21-48) acquires a file lock using proper-lockfile to prevent concurrent writes, executes a user-supplied updater function that mutates the store, and persists changes atomically.
saveAuthProfileStore(store, agentDir?) (lines 368-379) serializes the store to JSON using the current AUTH_STORE_VERSION and writes it to the correct path, either global or agent-specific.
Legacy Migration and External CLI Sync
The store system includes coerceLegacyStore (lines 50-89) to detect and convert old auth.json formats to the new profileId scheme, automatically migrating data and deleting the legacy file.
After every load operation, syncExternalCliCredentials pulls credentials stored by third-party CLIs (such as the claude CLI) into the store, ensuring ClosedClaw always has access to the latest tokens.
Practical Code Examples
The following examples demonstrate how to interact with authentication profiles in ClosedClaw.
// Example 1 – Load the global auth-profile store
import { ensureAuthProfileStore } from "./agents/auth-profiles.js";
const store = ensureAuthProfileStore(); // returns AuthProfileStore
console.log(store.profiles); // { "anthropic:default": { … } }
// Example 2 – Add a new Anthropic OAuth credential
import { updateAuthProfileStoreWithLock } from "./agents/auth-profiles/store.js";
await updateAuthProfileStoreWithLock({
updater: (s) => {
s.profiles["anthropic:my-bot"] = {
type: "oauth",
provider: "anthropic",
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
email: "dev@example.com",
};
return true; // indicate that the store was mutated
},
});
// Example 3 – Override auth order for a specific agent
import { ensureAuthProfileStore } from "./agents/auth-profiles.js";
const agentDir = "/tmp/ClosedClaw-agent/my-agent";
const store = ensureAuthProfileStore(agentDir);
store.order = store.order ?? {};
store.order["anthropic"] = ["anthropic:my-bot", "anthropic:default"];
// Persist the change (no lock needed if you already hold one)
import { saveAuthProfileStore } from "./agents/auth-profiles/store.js";
saveAuthProfileStore(store, agentDir);
Key Source Files
| File | Role |
|---|---|
src/agents/auth-profiles/types.ts |
Defines the credential unions, AuthProfileStore, and usage-stats structures. |
src/agents/auth-profiles/store.ts |
Core logic for loading, merging, persisting, and locking the store (including legacy migration and external-CLI sync). |
src/agents/auth-profiles/paths.ts |
Resolves file locations (auth-profiles.json, legacy auth.json, OAuth file). |
src/agents/auth-profiles/constants.ts |
Store version (AUTH_STORE_VERSION) and lock options. |
src/agents/auth-profiles/external-cli-sync.ts |
Integrates credentials managed by third-party CLIs. |
Summary
- Authentication profiles in ClosedClaw are stored in a single JSON file (
auth-profiles.json) following theAuthProfileStoreschema. - The schema supports three credential types—API keys, static tokens, and OAuth tokens—unified under the
AuthProfileCredentialunion. - Profile IDs follow the convention
<provider>:<name>(e.g.,anthropic:default). - The
src/agents/auth-profiles/store.tsmodule provides atomic, lock-protected operations viaupdateAuthProfileStoreWithLockand handles per-agent overrides, legacy migration, and external CLI synchronization. - Runtime statistics and rotation order are tracked via optional
usageStatsandorderfields.
Frequently Asked Questions
What is the AuthProfileStore format in ClosedClaw?
The AuthProfileStore is a versioned JSON schema defined in src/agents/auth-profiles/types.ts that structures how authentication credentials are persisted. It contains a version field, a profiles map keyed by profile IDs (e.g., anthropic:default), and optional fields for usage statistics, rotation order, and per-agent overrides. The format supports API keys, static tokens, and OAuth credentials through a discriminated union type.
How do I add a new authentication profile to ClosedClaw?
You add a profile by mutating the profiles map of an AuthProfileStore and persisting it with file locking. Import updateAuthProfileStoreWithLock from src/agents/auth-profiles/store.ts, provide an updater function that assigns a new credential object to a profile ID following the <provider>:<name> convention, and return true to indicate mutation. The helper handles atomic writes and prevents corruption from concurrent access.
Does ClosedClaw support per-agent authentication overrides?
Yes, ClosedClaw supports per-agent overrides through the ensureAuthProfileStore function. When you provide an agentDir argument, the function merges the global store with any agent-specific auth-profiles.json in that directory. This allows agents to define custom order arrays for credential rotation or override specific profiles while inheriting global credentials. The merge logic ensures that agent-specific settings take precedence without corrupting the global store.
How does ClosedClaw handle concurrent writes to auth-profiles.json?
ClosedClaw prevents race conditions using file locking via the updateAuthProfileStoreWithLock function in src/agents/auth-profiles/store.ts. This helper acquires a lock using proper-lockfile before executing the updater callback, ensuring that only one process can write to auth-profiles.json at a time. After the updater completes, the function serializes the store to JSON with the current AUTH_STORE_VERSION and writes it atomically to disk, releasing the lock only after the operation succeeds.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →