How to Set Up Encrypted Credentials in Agent-Native Using `saveCredential` and `resolveCredential`

Agent-Native automatically encrypts API keys and secrets using AES-256-GCM before storing them in the SQL settings table, exposing two helper functions—saveCredential and resolveCredential—to handle scoping, encryption, and decryption transparently.

The BuilderIO/agent-native repository provides a credentials subsystem that eliminates the need to manage environment variables for user-specific secrets. Instead of storing plaintext tokens, you use the context-aware helpers defined in packages/core/src/credentials/index.ts to persist encrypted values scoped to either individual users or entire organizations.

Understanding the Credential Encryption Architecture

Before calling the API functions, you need to understand how Agent-Native structures credential storage and encryption.

CredentialContext and Storage Keys

Every credential operation requires a CredentialContext object containing at minimum a userEmail string. Optionally, you can include an orgId to enable organization-scoped storage.

The system generates storage keys using these conventions:

  • User-scoped: u:<email>:credential:<KEY>
  • Org-scoped: o:<orgId>:credential:<KEY>

These keys map to rows in the settings table, where the value is encrypted using encryptSecretValue from packages/core/src/secrets/crypto.ts before persistence.

AES-256-GCM Encryption Flow

When you call saveCredential, the plain text value is passed to encryptSecretValue, which applies AES-256-GCM encryption. The ciphertext is then stored via the low-level settings API in packages/core/src/settings/store.ts. During retrieval, resolveCredential automatically decrypts the value using the corresponding decryption helper, returning the original plaintext to your application code.

Saving Credentials with saveCredential

The saveCredential function persists encrypted secrets and accepts three parameters: the credential key name, the raw secret value, and the context object.

User-Scoped Credentials (Default)

By default, credentials are tied to the individual user specified in the context:

import { saveCredential } from '@agent-native/core/credentials';

async function storeOpenAiKey(userEmail: string, rawKey: string) {
  await saveCredential('OPENAI_API_KEY', rawKey, {
    userEmail,
    // orgId omitted — stores under u:<email>:credential:OPENAI_API_KEY
  });
}

Organization-Scoped Credentials

To share credentials across an entire organization, explicitly pass scope: "org":

await saveCredential('STRIPE_SECRET', stripeKey, {
  userEmail: currentUser.email,
  orgId: currentOrg.id,
  scope: 'org',  // Stores under o:<orgId>:credential:STRIPE_SECRET
});

Organization-scoped values are accessible to all users within that organization unless a user has a personal override stored.

Retrieving Credentials with resolveCredential

The resolveCredential function reads credentials using a hierarchical lookup strategy defined in packages/core/src/credentials/index.ts:

  1. Check for a user-specific credential (u:<email>:credential:<KEY>)
  2. If not found and orgId is present, check for an org-scoped credential (o:<orgId>:credential:<KEY>)
  3. Return undefined if no credential exists

Unlike some credential managers, this implementation does not fall back to environment variables.

import { resolveCredential } from '@agent-native/core/credentials';

async function getOpenAiKey(ctx: { userEmail: string; orgId?: string }) {
  const apiKey = await resolveCredential('OPENAI_API_KEY', ctx);
  if (!apiKey) throw new Error('OpenAI API key not configured');
  return apiKey;
}

For simple presence checks without retrieving the value, use the hasCredential helper, which returns a boolean based on the same resolution logic.

Real-World Implementation Examples

The Agent-Native templates demonstrate production usage of these credentials helpers across different integration patterns.

Analytics API Endpoint

In templates/analytics/server/routes/api/credentials.post.ts, an HTTP endpoint exposes credential storage to the frontend:

import { saveCredential } from '@agent-native/core/credentials';
import { getUserContext } from '@/server/context';

export async function POST(req: Request) {
  const { key, value } = await req.json();
  const ctx = await getUserContext(req);  // { userEmail, orgId? }
  
  await saveCredential(key, value, ctx);
  return new Response('Credential encrypted and saved');
}

Calendar Integration Credentials

The calendar template in templates/calendar/server/lib/integration-credentials.ts shows both storage and retrieval patterns for third-party provider keys:

// Storage
await saveCredential(credentialKey(provider), apiKey, ctx);

// Retrieval (elsewhere in the codebase)
const apiKey = await resolveCredential('GOOGLE_API_KEY', ctx);
if (!apiKey) throw new Error('Google API key missing');

Mail/Google OAuth Configuration

For OAuth flows requiring client secrets, templates/mail/server/lib/google-auth.ts uses resolveCredential to fetch the Google client ID and secret without exposing them to the client bundle:

const clientId = await resolveCredential('GOOGLE_OAUTH_CLIENT_ID', ctx);
const clientSecret = await resolveCredential('GOOGLE_OAUTH_CLIENT_SECRET', ctx);

Summary

  • saveCredential in packages/core/src/credentials/index.ts encrypts values with AES-256-GCM and stores them in the SQL settings table using user-scoped (u:*) or org-scoped (o:*) keys.
  • resolveCredential implements a fallback hierarchy: user-specific credentials take precedence over organization-scoped values.
  • Scoped storage is controlled via the scope parameter ("user" default or "org") and the presence of orgId in the context.
  • Template implementations in Analytics, Calendar, and Mail demonstrate production-ready patterns for both HTTP endpoints and library utilities.

Frequently Asked Questions

What encryption algorithm does Agent-Native use for credentials?

Agent-Native uses AES-256-GCM (Galois/Counter Mode) via the encryptSecretValue function in packages/core/src/secrets/crypto.ts. This provides authenticated encryption, ensuring both confidentiality and integrity of stored secrets.

Can I store credentials without a user context?

No. The CredentialContext requires at minimum a userEmail to generate the storage key (u:<email>:credential:*). While you can store organization-scoped credentials that are shared across users, the operation itself must be initiated with a valid user context to maintain audit trails and access control.

How does resolveCredential handle missing credentials?

If a credential is not found in either the user-specific or organization-scoped store, resolveCredential returns undefined. It does not throw an error or fall back to environment variables. Your application code must explicitly check for undefined and handle the missing credential case appropriately.

Where are the encrypted values physically stored?

Encrypted values are stored in the settings table of your configured SQL database (SQLite, PostgreSQL, etc.). The raw encryption keys are never persisted alongside the ciphertext; decryption requires the application-level secret key configured in your Agent-Native environment.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →