# How LlmConnection Stores OAuth Identity Information After Token Exchange

> Learn how LlmConnection stores OAuth identity info after token exchange. Discover details on account UUID, email, org info, and verification timestamp preservation.

- Repository: [Craft Ai Agents/craft-agents-oss](https://github.com/craft-ai-agents/craft-agents-oss)
- Tags: internals
- Published: 2026-07-06

---

**After a successful OAuth token exchange, the system stores identity metadata—including account UUID, email, organization details, and verification timestamp—directly on the `LlmConnection` record through guarded field assignments in the RPC handler and explicit preservation logic in the storage layer.**

The craft-ai-agents/craft-agents-oss repository implements a fault-tolerant persistence mechanism for OAuth identity data within LLM connection configurations. When users authorize providers like Anthropic, the server captures the authenticated account and organization metadata from the token response and commits it to the connection's persistent storage. This allows the UI to identify when multiple connections resolve to the same underlying Claude account, preventing duplicate configurations.

## Parsing the OAuth Identity Response

The RPC handler responsible for LLM connection setup receives the OAuth flow result in [`src/packages/server-core/src/handlers/rpc/llm-connections.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/src/packages/server-core/src/handlers/rpc/llm-connections.ts). It extracts the `oauthIdentity` object from the request payload and performs guarded assignments to copy only the fields that are present. This "fail-soft" behavior prevents overwriting existing data with `undefined` values when the identity response contains partial information.

```typescript
// src/packages/server-core/src/handlers/rpc/llm-connections.ts
const oauthIdentity = setup.oauthIdentity
if (oauthIdentity?.account || oauthIdentity?.organization) {
  if (oauthIdentity.account?.uuid)   updates.oauthAccountUuid   = oauthIdentity.account.uuid
  if (oauthIdentity.account?.emailAddress) updates.oauthAccountEmail = oauthIdentity.account.emailAddress
  if (oauthIdentity.organization?.uuid)   updates.oauthOrganizationUuid = oauthIdentity.organization.uuid
  if (oauthIdentity.organization?.name)   updates.oauthOrganizationName = oauthIdentity.organization.name
  updates.oauthProfileVerifiedAt = Date.now()
}

```

Lines 63–74 demonstrate the conditional extraction that builds the `updates` object. The code specifically checks for the existence of each nested property before assignment, ensuring the update payload contains only valid identity data.

## Merging Updates with Existing Data

After constructing the update payload, the handler calls `updateLlmConnection(slug, updates)`. The generic storage logic in [`src/packages/shared/src/config/storage.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/src/packages/shared/src/config/storage.ts) merges these values with the existing connection record. Lines 2704–2709 implement explicit preservation logic that retains existing OAuth fields when they are not included in the current update.

```typescript
// src/packages/shared/src/config/storage.ts
oauthAccountUuid: updates.oauthAccountUuid !== undefined ? updates.oauthAccountUuid : existing.oauthAccountUuid,
oauthAccountEmail: updates.oauthAccountEmail !== undefined ? updates.oauthAccountEmail : existing.oauthAccountEmail,
oauthOrganizationUuid: updates.oauthOrganizationUuid !== undefined ? updates.oauthOrganizationUuid : existing.oauthOrganizationUuid,
oauthOrganizationName: updates.oauthOrganizationName !== undefined ? updates.oauthOrganizationName : existing.oauthOrganizationName,
oauthProfileVerifiedAt: updates.oauthProfileVerifiedAt !== undefined ? updates.oauthProfileVerifiedAt : existing.oauthProfileVerifiedAt,

```

This strict `undefined` check pattern ensures that OAuth identity information survives subsequent updates to unrelated connection properties, such as API endpoint URLs or model parameters.

## Schema Definition for OAuth Fields

The `LlmConnection` interface defines the storage contract for these identity fields in [`src/packages/shared/src/config/llm-connections.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/src/packages/shared/src/config/llm-connections.ts). Lines 92–100 reserve optional properties that accommodate the OAuth data structure while maintaining backward compatibility with connections that do not use OAuth authentication.

```typescript
// src/packages/shared/src/config/llm-connections.ts
/** --- Resolved Anthropic OAuth identity (issue #838) --- */
oauthAccountUuid?: string;
oauthAccountEmail?: string;
oauthOrganizationUuid?: string;
oauthOrganizationName?: string;
oauthProfileVerifiedAt?: number; // epoch ms

```

The `oauthProfileVerifiedAt` field stores the Unix epoch timestamp in milliseconds, marking when the identity was last verified. This metadata enables the UI to display the recency of the OAuth authorization and detect stale connections.

## Complete Implementation Flow

When implementing OAuth authorization for an Anthropic connection, the client initiates the flow and the server persists the resulting identity as follows:

```typescript
// Client side: Trigger the OAuth flow
await startAnthropicOAuth({ slug: connection.slug });

// Server side: Handler processes the callback
if (setup.oauthIdentity?.account || setup.oauthIdentity?.organization) {
  // Extract present fields only
  if (setup.oauthIdentity.account?.uuid)   updates.oauthAccountUuid   = setup.oauthIdentity.account.uuid;
  if (setup.oauthIdentity.account?.emailAddress) updates.oauthAccountEmail = setup.oauthIdentity.account.emailAddress;
  if (setup.oauthIdentity.organization?.uuid)   updates.oauthOrganizationUuid = setup.oauthIdentity.organization.uuid;
  if (setup.oauthIdentity.organization?.name)   updates.oauthOrganizationName = setup.oauthIdentity.organization.name;
  updates.oauthProfileVerifiedAt = Date.now();
}

// Persist to storage with merge protection
await updateLlmConnection(setup.slug, updates);

```

## Summary

- **Guarded Extraction**: The RPC handler in [`llm-connections.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/llm-connections.ts) copies only present OAuth fields to prevent `undefined` overwrites.
- **Explicit Preservation**: The storage layer uses strict `!== undefined` checks to retain existing identity data during partial updates.
- **Schema Support**: The `LlmConnection` interface defines optional fields for account UUID, email, organization UUID/name, and verification timestamp.
- **UI Integration**: Stored identity data enables the interface to flag duplicate connections pointing to the same Claude account.

## Frequently Asked Questions

### What specific OAuth fields does LlmConnection store?

The connection record stores five fields: `oauthAccountUuid` and `oauthAccountEmail` for the user account, `oauthOrganizationUuid` and `oauthOrganizationName` for the organization context, and `oauthProfileVerifiedAt` as a timestamp marking when the identity was verified. These fields are defined as optional strings and numbers in the TypeScript interface.

### How does the storage layer prevent overwriting existing OAuth data?

The update logic in [`storage.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/storage.ts) explicitly checks each OAuth field using `!== undefined` ternary operators. If a field is missing from the update payload, the storage layer retains the existing value rather than replacing it with `undefined`. This ensures that OAuth identity information persists across updates that modify unrelated connection properties.

### Why is the verification timestamp stored as an epoch number?

The `oauthProfileVerifiedAt` field uses a Unix epoch timestamp in milliseconds to provide a language-agnostic, sortable value that indicates when the OAuth identity was last validated. This allows the UI to display the relative age of the authorization and helps identify potentially stale connections that may require re-authentication.

### Where does the initial OAuth identity object originate?

The identity object originates from the OAuth provider's token exchange response, specifically after authorizing with Anthropic. The `oauthIdentity` payload is sent to the [`llm-connections.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/llm-connections.ts) RPC handler as part of the connection setup request, containing the authenticated account and organization details resolved during the OAuth flow.