# How Authentication Profiles Are Defined and Managed in ClosedClaw’s AuthProfileStore

> Learn how ClosedClaw defines and manages authentication profiles in its AuthProfileStore for API keys, static tokens, and OAuth credentials. Explore versioning, locking, and overrides.

- Repository: [aSafeLobotomy/closedclaw](https://github.com/asafelobotomy/closedclaw)
- Tags: how-to-guide
- Published: 2026-02-25

---

**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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/auth-profiles.json). It contains:

- **`version`**: The store schema version (controlled by `AUTH_STORE_VERSION` in [`src/agents/auth-profiles/constants.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/auth-profiles/constants.ts))
- **`profiles`**: A record mapping `profileId` strings to `AuthProfileCredential` objects
- **`order`** (optional): Per-agent overrides for credential rotation order
- **`lastGood`** (optional): Tracks the most recently successful profile per provider
- **`usageStats`** (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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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.

```typescript
// 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": { … } }

```

```typescript
// 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
  },
});

```

```typescript
// 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`](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/auth-profiles/types.ts) | Defines the credential unions, `AuthProfileStore`, and usage-stats structures. |
| [`src/agents/auth-profiles/store.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/auth-profiles/paths.ts) | Resolves file locations ([`auth-profiles.json`](https://github.com/asafelobotomy/closedclaw/blob/main/auth-profiles.json), legacy [`auth.json`](https://github.com/asafelobotomy/closedclaw/blob/main/auth.json), OAuth file). |
| [`src/agents/auth-profiles/constants.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/auth-profiles/constants.ts) | Store version (`AUTH_STORE_VERSION`) and lock options. |
| [`src/agents/auth-profiles/external-cli-sync.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/auth-profiles.json)) following the `AuthProfileStore` schema.
- The schema supports three credential types—**API keys**, **static tokens**, and **OAuth tokens**—unified under the `AuthProfileCredential` union.
- Profile IDs follow the convention `<provider>:<name>` (e.g., `anthropic:default`).
- The [`src/agents/auth-profiles/store.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/auth-profiles/store.ts) module provides **atomic, lock-protected operations** via `updateAuthProfileStoreWithLock` and handles **per-agent overrides**, **legacy migration**, and **external CLI synchronization**.
- Runtime statistics and rotation order are tracked via optional `usageStats` and `order` fields.

## 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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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.