PasskeyBase
N

Node.js SDK

@passkeybase/node

The @passkeybase/node package provides a server-side SDK for Node.js. It includes the Management API client, Express authentication middleware with JWKS validation, webhook signature verification, and enrollment token generation.

← All SDKs


Installation

Install via npm:

Terminal
npm install @passkeybase/node

Requirements: Node.js >= 20. The SDK uses nativefetch (no Axios or node-fetch required) and depends on jose for JWT signing and verification. ESM and CJS builds are both provided.


Quick Start

Initialize the ManagementClientwith your M2M credentials and tenant domain. The SDK handles OAuth2client_credentialstoken acquisition, caching, and automatic refresh.

index.ts
import { ManagementClient } from '@passkeybase/node';

const mgmt = new ManagementClient({
  domain: 'myapp.auth.ing',
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET',
});

// List all users
const users = await mgmt.users.list({ page: 1, perPage: 20 });
for (const user of users.data) {
  console.log(`${user.id}: ${user.name}`);
}

ManagementClient

The ManagementClient is the main entry point for the Management API. It exposes sub-clients for each resource type:users,credentials,clients,roles,tenant, andlogs.

Configuration
import { ManagementClient } from '@passkeybase/node';

const mgmt = new ManagementClient({
  domain: 'myapp.auth.ing',        // Required. Your tenant domain.
  clientId: 'YOUR_CLIENT_ID',      // Required. M2M OAuth client ID.
  clientSecret: 'YOUR_CLIENT_SECRET', // Required. M2M OAuth client secret.
  audience: 'https://myapp.auth.ing/api/v1/', // Optional. Custom audience.
  timeoutMs: 10_000,               // Optional. Request timeout in ms. Default: 10000.
  maxRetries: 3,                    // Optional. Retry transient failures. Default: 3.
});
OptionTypeDefaultDescription
domainstring--Your tenant domain (e.g., myapp.auth.ing).
clientIdstring--Machine-to-machine OAuth client ID from the dashboard.
clientSecretstring--Machine-to-machine OAuth client secret.
audiencestringhttps://{domain}/api/v1/Custom audience for token requests.
timeoutMsnumber10000Request timeout in milliseconds.
maxRetriesnumber3Maximum retries for transient failures (5xx, network errors).

Authentication Middleware (Express)

The requireAuth middleware validates Bearer JWTs from the Authorizationheader using your tenant's JWKS endpoint. On success, it populatesreq.auth with the verified token payload. JWKS keys are cached automatically.

server.ts
import express from 'express';
import { requireAuth } from '@passkeybase/node/middleware/express';

const app = express();

// Protect all /api routes with JWT verification
app.use('/api', requireAuth({
  domain: 'myapp.auth.ing',
  audience: 'https://api.myapp.com',
}));

// req.auth is populated with the verified token payload
app.get('/api/me', (req, res) => {
  res.json({
    userId: req.auth.sub,
    email: req.auth.email,
    name: req.auth.name,
    permissions: req.auth.permissions,
  });
});

app.listen(3000);

Middleware Options

requireAuth options
requireAuth({
  domain: 'myapp.auth.ing',           // Required. Tenant domain for JWKS lookup.
  audience: 'https://api.myapp.com',   // Required. Expected audience claim.
  algorithm: 'RS256',                  // Optional. Signing algorithm. Default: 'RS256'.
  clockToleranceSeconds: 5,            // Optional. Clock skew tolerance. Default: 5.
});
OptionTypeDefaultDescription
domainstring--Tenant domain used to fetch JWKS from https://{domain}/.well-known/jwks.json.
audiencestring--Expected aud claim in the JWT.
algorithmstring'RS256'Expected signing algorithm.
clockToleranceSecondsnumber5Clock skew tolerance for token expiration checks.

Webhook Verification

Verify incoming webhook signatures using HMAC-SHA256. The function validates the signature header and optionally checks the timestamp for replay protection.

Webhook handler
import express from 'express';
import { verifyWebhookSignature } from '@passkeybase/node';

const app = express();

app.post(
  '/webhooks/passkeybase',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    // Verify the HMAC-SHA256 signature
    verifyWebhookSignature(req.body, req.headers, {
      secret: process.env.WEBHOOK_SECRET!,
    });

    const event = JSON.parse(req.body.toString());

    switch (event.type) {
      case 'user.created':
        console.log('New user:', event.data.email);
        break;
      case 'credential.registered':
        console.log('New passkey for:', event.data.userId);
        break;
    }

    res.sendStatus(200);
  },
);

Verification Options

verifyWebhookSignature options
verifyWebhookSignature(rawBody, headers, {
  secret: 'whsec_...',                       // Required. Shared webhook secret.
  signatureHeader: 'x-passkeybase-signature', // Optional. Default shown.
  timestampHeader: 'x-passkeybase-timestamp', // Optional. Default shown.
  maxAgeSeconds: 300,                         // Optional. Replay protection window. Default: 300.
});

Important: Use express.raw() to receive the raw request body. If you use express.json(), the body will be parsed and the signature verification will fail.


Management API

Users

Create User

Create a user
const user = await mgmt.users.create({
  email: '[email protected]',
  name: 'Jane Doe',
  appMetadata: { plan: 'pro' },
});
console.log(`Created user: ${user.id}`);

List Users

List users with pagination
// List with pagination
const users = await mgmt.users.list({ page: 1, perPage: 20 });

for (const user of users.data) {
  console.log(`${user.id}: ${user.name} (${user.email})`);
}

console.log(`Total: ${users.total}`);

Get User

Get a user by ID
const user = await mgmt.users.get('usr_abc123');
console.log(`Name: ${user.name}`);
console.log(`Email: ${user.email}`);
console.log(`Logins: ${user.loginsCount}`);
console.log(`Last login: ${user.lastLogin}`);

Update User

Update a user
const user = await mgmt.users.update('usr_abc123', {
  name: 'Jane Smith',
  blocked: false,
  appMetadata: { tier: 'enterprise' },
});
console.log(`Updated: ${user.name}`);

Delete User

Deletes a user and all their associated passkeys.

Delete a user
await mgmt.users.delete('usr_abc123');

Get User Roles

Get roles for a user
const roles = await mgmt.users.getRoles('usr_abc123');
for (const role of roles) {
  console.log(`Role: ${role.name} (${role.id})`);
}

Credentials

List Credentials

List all passkey credentials for a specific user.

List credentials
const credentials = await mgmt.credentials.list('usr_abc123');

for (const cred of credentials) {
  console.log(`${cred.id}: ${cred.name} (${cred.type})`);
  console.log(`  Last used: ${cred.lastUsedAt}`);
}

Delete Credential

Revoke a specific passkey credential.

Delete a credential
await mgmt.credentials.delete('cred_xyz789');

Clients

Create Client

Create an OAuth client
const app = await mgmt.clients.create({
  name: 'My Web App',
  appType: 'spa',
  callbacks: ['https://myapp.com/callback'],
  allowedOrigins: ['https://myapp.com'],
  allowedLogoutUrls: ['https://myapp.com'],
});
console.log(`Client ID: ${app.clientId}`);

Rotate Secret

Rotate client secret
const result = await mgmt.clients.rotateSecret('pk_live_abc123');
console.log(`New secret: ${result.clientSecret}`);

Roles

Create Role

Create a role
const role = await mgmt.roles.create({
  name: 'admin',
  description: 'Administrator role',
});
console.log(`Role ID: ${role.id}`);

Assign and Remove Users

Manage role membership
// Assign users to a role
await mgmt.roles.assignUsers('role_abc123', ['usr_abc123', 'usr_def456']);

// List users in a role
const users = await mgmt.roles.listUsers('role_abc123');
for (const user of users) {
  console.log(`${user.id}: ${user.name}`);
}

// Remove users from a role
await mgmt.roles.removeUsers('role_abc123', ['usr_def456']);

Tenant Settings

Tenant settings
// Get tenant settings
const settings = await mgmt.tenant.get();
console.log(`Tenant: ${settings.tenantName}`);
console.log(`Domain: ${settings.domain}`);
console.log(`Environment: ${settings.environment}`);

// Update tenant settings
const updated = await mgmt.tenant.update({
  tenantName: 'My App (Production)',
  supportEmail: '[email protected]',
  sessionLifetime: 86400,
});

Audit Logs

Query audit logs
// List log entries with filters
const logs = await mgmt.logs.list({
  page: 1,
  perPage: 50,
  userId: 'usr_abc123',
  type: 'login_success',
  from: '2025-01-01T00:00:00Z',
});

for (const entry of logs.data) {
  console.log(`${entry.date}: ${entry.type} from ${entry.ip}`);
}

// Get a single log entry
const entry = await mgmt.logs.get('log_abc123');

Enrollment Tokens

Generate tokens for enrolling a passkey for an existing user. The SDK supports two approaches: client-side signed JWTs (no Management API call) and server-side enrollment tickets. See the Enrollment guide for the full flow.

Signed Token (HMAC-SHA256)

Creates a JWT signed locally using the client secret and thejose library. No API call required.

Create enrollment token
import { createEnrollmentToken } from '@passkeybase/node';

// Create a signed enrollment token (JWT with HMAC-SHA256)
const token = await createEnrollmentToken({
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET',
  userId: 'usr_abc123',
  email: '[email protected]',
  expiresInSeconds: 3600, // 1 hour (default)
});

// Return to the frontend for passkey enrollment
// res.json({ enrollmentToken: token });

Server-Side Enrollment Ticket

Creates an enrollment ticket via the Management API with a redirect URL.

Create enrollment ticket
// Or create a server-side enrollment ticket via the Management API
const ticket = await mgmt.users.createEnrollmentTicket('usr_abc123');
console.log(`Ticket URL: ${ticket.url}`);
console.log(`Expires at: ${ticket.expiresAt}`);

Full Express Example

A complete endpoint that verifies the user's JWT and returns an enrollment token for the frontend SDK.

Enrollment token endpoint
import express from 'express';
import { requireAuth } from '@passkeybase/node/middleware/express';
import { createEnrollmentToken } from '@passkeybase/node';

const app = express();

app.post(
  '/api/enrollment-token',
  requireAuth({ domain: 'myapp.auth.ing', audience: 'https://api.myapp.com' }),
  async (req, res) => {
    const token = await createEnrollmentToken({
      clientId: process.env.PASSKEYBASE_CLIENT_ID!,
      clientSecret: process.env.PASSKEYBASE_CLIENT_SECRET!,
      userId: req.auth.sub,
      email: req.auth.email!,
    });

    res.json({ enrollmentToken: token });
  },
);

Error Handling

All SDK methods throw typed errors extendingPasskeyBaseError. Use instanceof checks for granular error handling.

Error handling
import {
  ManagementClient,
  ApiError,
  AuthenticationError,
  NotFoundError,
  RateLimitError,
  PasskeyBaseError,
} from '@passkeybase/node';

const mgmt = new ManagementClient({
  domain: 'myapp.auth.ing',
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET',
});

try {
  const user = await mgmt.users.get('usr_nonexistent');
} catch (err) {
  if (err instanceof NotFoundError) {
    console.log(`Not found: ${err.message}`);   // "resource '/users/usr_nonexistent' not found"
    console.log(`Status: ${err.statusCode}`);    // 404
  } else if (err instanceof RateLimitError) {
    console.log(`Rate limited. Retry after: ${err.retryAfterMs}ms`);
  } else if (err instanceof AuthenticationError) {
    console.log(`Auth error: ${err.message}`);
  } else if (err instanceof ApiError) {
    console.log(`API error ${err.statusCode}: ${err.message}`);
  }
}
Error ClassDescription
PasskeyBaseErrorBase error class. Has code and message properties.
ApiErrorAPI returned an error response. Has statusCode and body.
AuthenticationErrorInvalid or expired M2M credentials, or JWT verification failure.
NotFoundError404 Not Found -- the requested resource does not exist.
RateLimitError429 Too Many Requests. Includes retryAfterMs in milliseconds.

Next steps