Package Exports
- @keychains/server-sdk
Readme
@keychains/server-sdk
BETA — This SDK is under active development. APIs may change.
Server SDK for Keychains.dev — give your server-side app secure access to user credentials (OAuth tokens, API keys) through the Keychains proxy.
Setup
npm install @keychains/server-sdk
npx @keychains/server-sdk register myapp.comThe register script will:
- Generate an RSA keypair and write
jwks.jsonto the right location for your project - Walk you through DNS verification
- Add
KEYCHAINS_*env vars to your.env
Quickstart
import { KeychainsApp } from '@keychains/server-sdk';
const keychains = KeychainsApp.fromEnv();Option 1: Approval at request time (recommended)
Everything is transparent until the proxy request — identical to how keychains curl works. A wildcard permission is created automatically, and the auth URL only surfaces when the user actually needs to connect a service.
// One-liner — fetch through the proxy:
try {
const res = await keychains.forUser(userId).withTask('Project Albatros').fetch('https://api.github.com/user');
const data = await res.json();
} catch (err) {
if (err instanceof KeychainsAppError && err.approvalUrl) {
// User hasn't connected GitHub yet — show them this URL
console.log(`Please authorize: ${err.approvalUrl}`);
}
}Or step by step:
// Get a token (creates a wildcard permission if needed)
const { token } = await keychains.forUser(userId).withTask('Project Albatros').getToken();
// Use the token for one or more proxy requests
const res = await keychains.fetch('https://api.github.com/user', { permissionToken: token });When to use: Quick prototyping, AI agents, cases where you don't know upfront which services the user will need.
Option 2: Pre-approving requests
Create a scoped permission with specific scopes. The user approves upfront, then all subsequent requests go through without interruption.
const scopes = ['gmail.com::oauth2::read', 'linear.app::oauth2::read'];
const permission = await keychains.forUser(userId).withTask('Project Albatros').getPermission(scopes);
// Show approval URL to the user
console.log('Please approve at: ' + permission.approvalUrl());
// Check if approved (poll or after redirect)
if (await permission.isApproved()) {
const res = await permission.fetch('https://gmail.googleapis.com/gmail/v1/users/me/messages');
// or: const { token } = await permission.getToken();
}When to use: Apps with explicit user consent UIs, when you know exactly which services are needed.
API Reference
KeychainsApp
// From environment variables (recommended)
const keychains = KeychainsApp.fromEnv();
// Manual configuration
const keychains = new KeychainsApp({
domain: 'myapp.com',
privateKey: process.env.KEYCHAINS_PRIVATE_KEY!,
keyId: 'key-1',
serverUrl: 'https://keychains.dev', // optional
});
keychains.setAppId(process.env.KEYCHAINS_APP_ID!);Fluent API
| Method | Returns | Description |
|---|---|---|
.forUser(userId) |
UserContext |
Scope operations to a user |
.forUser(id).withTask(name) |
TaskContext |
Scope to a user + task |
.forUser(id).withTask(name).fetch(url, opts?) |
Response |
Wildcard token + proxied fetch |
.forUser(id).withTask(name).getToken() |
TokenResult |
Wildcard token for manual fetch |
.forUser(id).withTask(name).getPermission(scopes?) |
Permission |
Scoped permission for pre-approval |
.forUser(id).getTokenForTask(name) |
TokenResult |
Shortcut for .withTask(name).getToken() |
.forUser(id).getPermissionForTask(name, scopes) |
Permission |
Shortcut for .withTask(name).getPermission(scopes) |
Lower-level methods
| Method | Description |
|---|---|
.createPermission(opts) |
Create a permission request |
.listPermissions(appUserId) |
List permissions for a user |
.getPermissionStatus(id, appUserId) |
Check permission status |
.mintToken(permissionId, appUserId, ttl?) |
Mint a short-lived token |
.fetch(url, { permissionToken }) |
Proxied fetch with a token |
.register({ verify? }) |
DNS registration flow |
.delegate(permissionId, appUserId, opts) |
Delegate access to VMs |
Permission
| Method | Returns | Description |
|---|---|---|
.approvalUrl() |
string | undefined |
URL for user to approve |
.isApproved() |
boolean |
Poll if permission is active |
.getToken(ttl?) |
TokenResult |
Mint a token |
.fetch(url, opts?) |
Response |
Mint token + proxied fetch |
Error Handling
import { KeychainsAppError } from '@keychains/server-sdk';
try {
const res = await keychains.forUser(userId).withTask('My Task').fetch(url);
} catch (err) {
if (err instanceof KeychainsAppError) {
if (err.approvalUrl) {
// User needs to authorize — redirect or show the URL
console.log(`Authorize: ${err.approvalUrl}`);
} else {
console.error(`${err.code}: ${err.message}`);
}
}
}| Code | Description |
|---|---|
authorization_required |
Proxy request needs user approval (has approvalUrl) |
missing_env |
Required environment variables not set |
not_registered |
App ID not set — run register or call setAppId() |
unauthorized |
Invalid or expired JWT |
not_found |
Permission or app not found |
forbidden |
Permission revoked or app revoked |
Registration Flow
1. Run the register script
npx @keychains/server-sdk register myapp.comThis generates a keypair, writes jwks.json, and walks you through DNS verification.
2. Serve the JWKS endpoint
Your domain must serve the JWKS file at:
https://myapp.com/.well-known/keychains.dev/jwks.jsonFor Next.js / static sites, the register script places it in public/ by default.
3. DNS verification
Add a TXT record as instructed by the script. Once verified, your app is registered and the env vars are written to .env.
Delegation
Delegate access to VMs or sub-agents:
const delegate = await keychains.delegate(permissionId, userId, {
publicKey: vmPublicKey,
scopes: ['github.com::oauth2::repo'],
});
// Or use bootstrap token for self-registration
const delegate = await keychains.delegate(permissionId, userId, {
useBootstrapToken: true,
scopes: ['github.com::oauth2::repo'],
});
// delegate.bootstrapToken → send to VMSecurity Model
- JWKS Authentication: Your app signs JWTs with a private key; Keychains verifies via your published JWKS
- Domain Verification: DNS TXT record proves domain ownership
- User Sovereignty: Users authenticate on keychains.dev and explicitly authorize access. Compromising your signing key does NOT grant access to user credentials.
- Key Rotation: Add new keys to JWKS before removing old ones. 24-hour grace period on key disappearance.
Environment Variables
| Variable | Required | Description |
|---|---|---|
KEYCHAINS_DOMAIN |
Yes | Your registered domain |
KEYCHAINS_PRIVATE_KEY |
Yes | PEM-encoded private key |
KEYCHAINS_KEY_ID |
Yes | Key ID (kid) from your JWKS |
KEYCHAINS_APP_ID |
No | App ID (auto-set by register script) |
KEYCHAINS_SERVER_URL |
No | Override server URL (default: https://keychains.dev) |
License
Proprietary — Interagentic Inc.