JSPM

@keychains/server-sdk

0.0.2
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 6
  • Score
    100M100P100Q30342F
  • License PROPRIETARY

Server SDK for Keychains.dev (BETA) — register trusted apps, create permissions, mint tokens, and make proxy calls via JWKS authentication

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.com

The register script will:

  1. Generate an RSA keypair and write jwks.json to the right location for your project
  2. Walk you through DNS verification
  3. Add KEYCHAINS_* env vars to your .env

Quickstart

import { KeychainsApp } from '@keychains/server-sdk';

const keychains = KeychainsApp.fromEnv();

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.com

This 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.json

For 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 VM

Security 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.