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:
- Check for a user-specific credential (
u:<email>:credential:<KEY>) - If not found and
orgIdis present, check for an org-scoped credential (o:<orgId>:credential:<KEY>) - Return
undefinedif 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
saveCredentialinpackages/core/src/credentials/index.tsencrypts values with AES-256-GCM and stores them in the SQLsettingstable using user-scoped (u:*) or org-scoped (o:*) keys.resolveCredentialimplements a fallback hierarchy: user-specific credentials take precedence over organization-scoped values.- Scoped storage is controlled via the
scopeparameter ("user"default or"org") and the presence oforgIdin 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →