npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vizualkei/sophid-server-sdk

v0.33.0

Published

SophID Web Server SDK - Biometric authentication helpers for server endpoints

Readme

SophID Server SDK

Server-side TypeScript SDK for partner web applications integrating SophID biometric authentication. It runs in your backend API and handles:

  • Issuing short-lived Biometric Session Tokens (BST) with HMAC signatures and optional encrypted user binding (ebuid).
  • Validating signed Biometric Result Tokens (BRT) returned by the SophID server.
  • Enforcing expiry, replay protection, and operation allowlists.

You need a partner API key from SophID and the SophID server's ES256 public key.

Installation

pnpm add @vizualkei/sophid-server-sdk

Quick Start (Recommended)

Use the SophidServerHelper singleton for the simplest integration:

import { sophidServerHelper } from '@vizualkei/sophid-server-sdk';

// Initialize once at server startup
const sophidHelper = sophidServerHelper.init({
  apiKey: process.env.SOPHID_API_KEY!,
  publicKeyPem: process.env.SOPHID_JWT_PUBLIC_KEY!,
});

// In your BST endpoint (POST /api/biometric-session):
const { bst, expiresAt } = await sophidHelper.createBiometricSessionToken(biometricUserId);

// In your BRT endpoint (POST /api/biometric-result):
const { claims, op } = await sophidHelper.verifyBiometricResultToken(brt, {
  requireBst: true,
  expectedBuid: session.user.biometricUserId,
  requireSuccess: true,
});

SophidServerHelper (Recommended)

The SophidServerHelper is the recommended integration surface. It wraps the lower-level pool and token classes, providing a clean two-method API for BST issuance and BRT validation.

Initialization

import { sophidServerHelper } from '@vizualkei/sophid-server-sdk';

const sophidHelper = sophidServerHelper.init({
  apiKey: '<partner-api-key>',
  publicKeyPem: '<sophid-es256-public-key-pem>',
  issuer: 'sophid',                    // optional, JWT issuer check
  audience: 'your-app',                   // optional, JWT audience check
  sessionConfig: {
    ttlMs: 5 * 60 * 1000,                 // BST TTL (default: 5 minutes)
    maxFutureSkewMs: 30 * 1000,            // clock skew tolerance (default: 30s)
  },
  defaultAllowedOps: ['enroll', 'unenroll', 'authenticate', 'restore'],
  bstFailureStatus: 403,                  // HTTP status for BST failures
});

SophidServerHelperConfig

| Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | apiKey | string | Yes | — | Partner API key for HMAC-signing BSTs | | publicKeyPem | string | Yes | — | SophID server ES256 public key (PEM) | | issuer | string | No | 'sophid' | Expected JWT iss claim | | audience | string | No | — | Expected JWT aud claim | | sessionConfig.ttlMs | number | No | 300000 | BST time-to-live (ms) | | sessionConfig.maxFutureSkewMs | number | No | 30000 | Max future clock skew (ms) | | defaultAllowedOps | Iterable<string> | No | — | Default operation allowlist | | bstFailureStatus | number | No | 403 | HTTP status code for BST failures |

createBiometricSessionToken

Issue a BST for a biometric operation.

const { bst, expiresAt, token, entry } = await sophidHelper.createBiometricSessionToken(
  biometricUserId,  // optional — omit for enrollment
  metadata           // optional — attached to pool entry
);

Parameters:

| Name | Type | Description | |------|------|-------------| | biometricUserId | string? | User ID to bind via ebuid. Omit for enrollment. | | metadata | Record<string, unknown>? | Custom metadata stored with the session entry |

Returns: BiometricSessionIssueResult

| Field | Type | Description | |-------|------|-------------| | bst | string | Serialized BST JSON string | | expiresAt | number | Expiration timestamp (ms) | | token | BiometricSessionTokenInterface | Token object | | entry | BiometricSessionPoolEntry | Pool entry |

verifyBiometricResultToken

Validate a BRT and optionally consume the embedded BST.

const { claims, op, bst } = await sophidHelper.verifyBiometricResultToken(brt, {
  requireBst: true,
  expectedBuid: session.user.biometricUserId,
  requireSuccess: true,
  allowedOps: ['enroll', 'authenticate', 'restore', 'unenroll'],
});

Parameters:

| Name | Type | Description | |------|------|-------------| | brt | string | The BRT JWT string | | options | VerifyBiometricResultOptions | Validation options |

VerifyBiometricResultOptions:

| Field | Type | Default | Description | |-------|------|---------|-------------| | requireBst | boolean | true | Validate and consume BST from claims | | expectedBuid | string \| null | — | Expected biometric user ID (matched against decrypted ebuid) | | requireSuccess | boolean | false | Require claims.success === true | | allowedOps | Set<string> \| string[] | defaultAllowedOps | Allowed operation types | | useClaimsError | boolean | false | Use claims.error as the failure message | | failureMessage | string | — | Custom failure message | | bstFailureStatus | number | 403 | HTTP status for BST failures |

Returns: { claims: BiometricResultClaims; op: string; bst: string }

Throws: Error with .status property for HTTP status codes.

Singleton Access

import { sophidServerHelper } from '@vizualkei/sophid-server-sdk';

// After init(), use directly:
const { bst } = await sophidServerHelper.createBiometricSessionToken(userId);
const { claims } = await sophidServerHelper.verifyBiometricResultToken(brt, opts);

// Or get the instance:
const helper = sophidServerHelper.get();

Integration Examples

Example: Next.js API Routes

// app/api/_lib/SophidServerHelper.ts
import { sophidServerHelper } from '@vizualkei/sophid-server-sdk';

export const sophidHelper = sophidServerHelper.init({
  apiKey: process.env.SOPHID_API_KEY!,
  publicKeyPem: process.env.SOPHID_JWT_PUBLIC_KEY!,
  sessionConfig: { ttlMs: 5 * 60 * 1000, maxFutureSkewMs: 30 * 1000 },
});

// app/api/biometric-session/route.ts
import { sophidHelper } from '../_lib/SophidServerHelper';

export async function POST(req: Request) {
  const session = await getSession(req);
  const biometricUserId = session.user.biometricUserId;  // undefined for new users

  const { bst, expiresAt } = await sophidHelper.createBiometricSessionToken(biometricUserId);
  return Response.json({ bst, expiresAt: new Date(expiresAt).toISOString() });
}

// app/api/biometric-result/route.ts
import { sophidHelper } from '../_lib/SophidServerHelper';

export async function POST(req: Request) {
  const session = await getSession(req);
  const { brt } = await req.json();

  const { claims, op } = await sophidHelper.verifyBiometricResultToken(brt, {
    requireBst: true,
    expectedBuid: session.user.biometricUserId,
    requireSuccess: true,
    allowedOps: new Set(['enroll', 'unenroll', 'authenticate', 'restore']),
  });

  switch (op) {
    case 'enroll':
      await updateUser(session.user.id, { biometricUserId: claims.userId });
      break;
    case 'unenroll':
      await updateUser(session.user.id, { biometricUserId: null });
      break;
  }

  return Response.json({ success: true, operation: op });
}

Example: Express.js Routes

import express from 'express';
import { sophidServerHelper } from '@vizualkei/sophid-server-sdk';

const router = express.Router();

const sophidHelper = sophidServerHelper.init({
  apiKey: process.env.SOPHID_API_KEY!,
  publicKeyPem: process.env.SOPHID_JWT_PUBLIC_KEY!,
  issuer: 'sophid',
  defaultAllowedOps: ['enroll', 'unenroll', 'authenticate', 'restore', 'checkin'],
});

router.post('/biometric-session', requireAuth, async (req, res) => {
  const biometricUserId = req.user.biometricUserId;
  const { bst, expiresAt } = await sophidHelper.createBiometricSessionToken(biometricUserId);
  res.json({ bst, expiresAt: new Date(expiresAt).toISOString() });
});

router.post('/biometric-result', requireAuth, async (req, res) => {
  try {
    const { claims, op } = await sophidHelper.verifyBiometricResultToken(req.body.brt, {
      requireBst: true,
      expectedBuid: req.user.biometricUserId,
      requireSuccess: true,
    });
    // Process based on op...
    res.json({ success: true, operation: op });
  } catch (err) {
    res.status(err.status || 500).json({ error: err.message });
  }
});

// Station check-in (no BST — trusted client)
router.post('/biometric-checkin', requireCheckinApiKey, async (req, res) => {
  try {
    const { claims, op } = await sophidHelper.verifyBiometricResultToken(req.body.brt, {
      requireBst: false,  // Station is trusted, no BST
      requireSuccess: true,
      allowedOps: ['checkin'],
    });
    // Update ticket records...
    res.json({ success: true, operation: op });
  } catch (err) {
    res.status(err.status || 500).json({ error: err.message });
  }
});

Required Server Endpoints

Partner servers must expose two endpoints:

1. POST /api/biometric-session

Mint a BST for a single biometric operation.

Response:

{
  "bst": "{\"sid\":\"<uuid>\",\"ts\":1700000000000,\"ebuid\":\"<base64url>\",\"sig\":\"<base64url>\"}",
  "expiresAt": "2026-01-01T00:05:00.000Z"
}

2. POST /api/biometric-result

Receive and validate a BRT from the webapp.

Request:

{ "brt": "<signed-jwt>" }

Response:

{ "success": true, "operation": "enroll" }

3. POST /api/biometric-checkin (optional)

For station/kiosk apps. Validates BRT without BST (trusted client model).

Auth: Authorization: Bearer <checkinApiKey>

Request:

{ "brt": "<signed-jwt>" }

Low-Level API

For advanced use cases, you can use the individual classes directly.

BiometricSessionIdPool

Manages BST generation with HMAC signing, ebuid encryption, and replay protection.

import { BiometricSessionIdPool } from '@vizualkei/sophid-server-sdk';

const pool = new BiometricSessionIdPool({
  apiKey: process.env.SOPHID_API_KEY!,
  ttlMs: 5 * 60 * 1000,
});

// Generate a BST
const { token, entry } = await pool.generate(biometricUserId);
const bstString = token.toTokenString();

// Remove/consume a session
const consumed = await pool.remove(sessionId);

// Decrypt ebuid
const userId = pool.decryptBioUserId(ebuid);

BiometricSessionToken

Parse and validate a BST string.

import { BiometricSessionToken } from '@vizualkei/sophid-server-sdk';

const bst = new BiometricSessionToken(bstJsonString);
const sessionId = bst.getSessionId();
const timestamp = bst.getTimeStamp();
const ebuid = bst.getEbuid();
const serialized = bst.toTokenString();

// Validate against pool
const result = await bst.validate(pool, { expectedBuid: 'user-123' });
if (!result.ok) {
  console.error(result.reason, result.message);
}

Validation reasons: invalid_format, future_timestamp, expired, missing_api_key, invalid_signature, not_found, mismatch, ebuid_mismatch, ebuid_decrypt_failed, expected_buid_mismatch

BiometricResultToken

Validate a BRT JWT.

import { BiometricResultToken } from '@vizualkei/sophid-server-sdk';

const verifier = new BiometricResultToken({
  publicKey: process.env.SOPHID_JWT_PUBLIC_KEY!,
  issuer: 'sophid',
  audience: 'your-app',
});

const claims = await verifier.validate(brt, {
  sessionPool: pool,       // enables BST validation + consumption
  requireBst: true,
  expectedBuid: 'user-123',
});

Concepts

BST (Biometric Session Token)

A short-lived JSON string authorizing a single biometric operation.

{"sid":"<uuid>","ts":1700000000000,"ebuid":"<base64url>","sig":"<base64url>"}

| Field | Description | |-------|-------------| | sid | Session UUID | | ts | Timestamp (ms since epoch) | | ebuid | Encrypted biometric user ID (optional, empty for enrollment) | | sig | HMAC-SHA256 of <sid>.<ts>.<ebuid> using the partner API key |

  • TTL: 5 minutes (configurable)
  • Single-use: consumed from the pool on validation

ebuid (Encrypted Biometric User ID)

Binds a session to a specific user, preventing session hijacking.

  • Encryption: AES-256-GCM, key = SHA-256(partner API key)
  • Format: base64url(iv[12] + authTag[16] + ciphertext)
  • Plaintext: <biometricUserId>:<randomPadding>
  • Required for: authenticate, authenticate-on-device, unenroll
  • Empty for: enroll
  • Optional for: restore

BRT (Biometric Result Token)

An ES256 JWT signed by the SophID server.

Common claims: success, op, callbackId, bst, userId, enrollmentId, userName, enrolledAt, authenticatedAt, eventId, redeemedTickets, error, reason, message, cancelled, iss, aud, jti, iat, exp


Thread Safety

The BiometricSessionIdPool uses async-mutex for thread-safe access. All operations (generate, remove, cleanup) are serialized. Safe for concurrent Node.js async request handlers and Worker threads.

Multi-process limitation: The built-in pool is in-memory. For multi-process deployments (cluster mode, PM2, multiple replicas), implement a custom BiometricSessionIdPoolInterface backed by Redis or a database.


Types Reference

BiometricSessionTokenPayload

type BiometricSessionTokenPayload = {
  sessionId: string;
  timeStamp: number;
  ebuid?: string;
  signature?: string;
};

BiometricSessionPoolEntry

type BiometricSessionPoolEntry = {
  sessionId: string;
  issuedAt: number;
  expiresAt: number;
  ebuid?: string;
  metadata?: Record<string, unknown>;
};

BiometricSessionIssue

type BiometricSessionIssue = {
  token: BiometricSessionTokenInterface;
  entry: BiometricSessionPoolEntry;
};

BiometricSessionIssueResult

type BiometricSessionIssueResult = {
  bst: string;
  token: BiometricSessionTokenInterface;
  entry: BiometricSessionPoolEntry;
  expiresAt: number;
};

BiometricSessionValidationResult

type BiometricSessionValidationResult = {
  ok: boolean;
  reason?: string;
  message?: string;
  entry?: BiometricSessionPoolEntry;
};

BiometricResultClaims

type BiometricResultClaims = Record<string, any>;

Configuration

Config Types

// BST validation config
type BiometricSessionTokenConfig = {
  ttlMs?: number;            // default: 300000 (5 min)
  maxFutureSkewMs?: number;  // default: 30000 (30 sec)
  logger?: Logger;
};

// Pool config (extends token config)
type BiometricSessionPoolConfig = BiometricSessionTokenConfig & {
  apiKey?: string;
};

// BRT validation config
type BiometricResultTokenConfig = {
  publicKey: string;         // required — ES256 PEM
  issuer?: string;
  audience?: string;
  logger?: Logger;
};