Skip to content

Handler.keyManager

Creates a server handler that manages WebAuthn credential public keys. This handler stores and retrieves the public keys associated with passkey credentials, enabling users to access their accounts from any device.

Usage

server.ts
import { Handler, Kv } from 'tempo.ts/server'
 
const handler = Handler.keyManager({
  kv: Kv.memory(),
  path: '/keys',
  rp: 'example.com',
})

Then plug handler into your server framework of choice. For example:

createServer(handler.listener) // Node.js
Bun.serve(handler) // Bun
Deno.serve(handler) // Deno
app.all('*', c => handler.fetch(c.request)) // Elysia
app.use(handler.listener) // Express
app.use(c => handler.fetch(c.req.raw)) // Hono
export const GET = handler.fetch // Next.js
export const POST = handler.fetch // Next.js

How It Works

The key manager handler exposes three endpoints:

GET /{path}/challenge

Generates a random challenge for WebAuthn credential creation. The challenge is stored temporarily (with a 5-minute expiration) to be verified later during credential registration.

GET /{path}/:credentialId

Retrieves the public key for a given credential ID. This is used when a user authenticates with an existing passkey.

POST /{path}/:credentialId

Stores a public key for a new credential. This endpoint:

  1. Verifies the challenge was previously issued and is still valid
  2. Validates the credential's clientDataJSON.type is 'webauthn.create'
  3. Verifies the origin matches the configured RP ID (if set)
  4. Extracts and stores the public key from the authenticator data

Parameters

kv

  • Type: Kv
  • Required: Yes

The key-value store to use for persisting challenges and public keys. tempo.ts provides adapters for common KV stores:

import { Kv } from 'tempo.ts/server'
 
// In-memory store (for development/testing)
const kv = Kv.memory()
 
// Cloudflare KV
import { env } from 'cloudflare:workers'
const kv = Kv.cloudflare(env.KEY_STORE)

path

  • Type: string

The base path where the handler will listen for requests. All three endpoints (/challenge, /:credentialId) will be mounted under this path.

import { Handler } from 'tempo.ts/server'
 
const handler = Handler.keyManager({
  // ... other options
  path: '/api/keys', 
})
 
// Endpoints will be:
// GET  /api/keys/challenge
// GET  /api/keys/:credentialId
// POST /api/keys/:credentialId

rp

  • Type: string | { id: string, name?: string }

The Relying Party (RP) identifier and name. This is used to:

  • Include RP information in the challenge response
  • Verify the origin of credential registration requests
import { Handler } from 'tempo.ts/server'
 
// Simple string (uses as both id and name)
const handler = Handler.keyManager({
  // ... other options
  rp: 'example.com', 
})
 
// Object with custom name
const handler2 = Handler.keyManager({
  // ... other options
  rp: { 
    id: 'example.com', 
    name: 'Example App', 
  }, 
})

Storage Schema

The handler uses the following key patterns in the KV store:

  • challenge:{hex} - Stores issued challenges
  • credential:{credentialId} - Stores public keys