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

@sp-uvb/hono

v0.1.0

Published

Production-grade Hono middleware for Universal Verification Broker (UVB) authentication

Downloads

8

Readme

@sp-uvb/hono

Production-grade Hono middleware for Universal Verification Broker (UVB) authentication. Built for modern JavaScript runtimes including Node.js, Bun, Deno, Cloudflare Workers, and edge environments.

Features

  • 🔐 Automatic Session Validation - Validates sessions on every request
  • 🌐 Multi-Runtime Support - Works on Node.js, Bun, Deno, Cloudflare Workers, and edge runtimes
  • 🎯 Context-Based API - Access session via c.get('uvbSession')
  • 🛡️ MFA Factor Guards - Require specific authentication factors per route
  • 👤 Resource Ownership - Verify users own resources they're accessing
  • Edge-Ready - Optimized for Cloudflare Workers and edge compute
  • 🔧 Fully Customizable - Override error handlers, customize behavior
  • 💪 TypeScript First - Complete type safety with Hono's type system

Installation

npm install @sp-uvb/hono hono
# or
bun add @sp-uvb/hono hono
# or
deno add @sp-uvb/hono hono

Quick Start

Basic Setup

import { Hono } from 'hono';
import { uvbAuth } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

app.get('/', (c) => 'Hello UVB!');

app.get('/profile', (c) => {
  const session = c.get('uvbSession');
  if (!session) {
    return c.json({ error: 'Not authenticated' }, 401);
  }
  return c.json({
    userId: session.userId,
    factors: session.factorsVerified,
  });
});

export default app;

Required Authentication

import { Hono } from 'hono';
import { uvbAuth, uvbRequire } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

app.get('/dashboard', uvbRequire(), (c) => {
  const session = c.get('uvbSession')!;
  return c.json({
    welcome: `Hello, user ${session.userId}!`,
    sessionId: session.sessionId,
  });
});

export default app;

Configuration Options

UvbMiddlewareOptions

interface UvbMiddlewareOptions {
  // Required: UVB server URL
  uvbUrl: string;

  // Required: Your tenant ID
  tenantId: string;

  // Optional: Cookie name for session token (default: 'uvb_session')
  cookieName?: string;

  // Optional: Header name for session token (default: 'x-uvb-session')
  headerName?: string;

  // Optional: Paths to exclude from authentication (default: [])
  excludePaths?: string[];

  // Optional: Require authentication on all routes (default: false)
  required?: boolean;

  // Optional: Environment variable accessor for edge runtimes
  getEnv?: (c: Context) => { UVB_URL?: string; UVB_TENANT_ID?: string };

  // Optional: Custom error handler
  onError?: (c: Context, error: Error) => Response | Promise<Response>;

  // Optional: Custom unauthorized handler
  onUnauthorized?: (c: Context) => Response | Promise<Response>;
}

Session Object

The uvbSession is attached to context via c.get('uvbSession'):

interface UvbSession {
  userId: string; // Unique user identifier
  tenantId: string; // Tenant identifier
  sessionId: string; // Session identifier
  factorsVerified: string[]; // List of verified authentication factors
  expiresAt: Date; // Session expiration timestamp
  metadata?: Record<string, any>; // Optional session metadata
}

Authentication Guards

Basic Guard

Require authentication for specific routes:

import { Hono } from 'hono';
import { uvbAuth, uvbRequire } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

app.get('/public', (c) => c.text('Anyone can access'));

app.get('/protected', uvbRequire(), (c) => {
  const session = c.get('uvbSession')!;
  return c.json({ userId: session.userId });
});

export default app;

MFA Factor Requirements

Require All Factors

import { Hono } from 'hono';
import { uvbAuth, uvbRequireAllFactors } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

app.post('/transfer', uvbRequireAllFactors(['password', 'totp', 'webauthn']), async (c) => {
  const session = c.get('uvbSession')!;
  const body = await c.req.json();
  return c.json({
    transfer: 'authorized',
    amount: body.amount,
    userId: session.userId,
  });
});

export default app;

Require Any Factor

import { uvbRequireAnyFactor } from '@sp-uvb/hono';

app.get('/settings', uvbRequireAnyFactor(['totp', 'webauthn', 'sms']), (c) => {
  const session = c.get('uvbSession')!;
  return c.json({
    userId: session.userId,
    mfaEnabled: true,
  });
});

Resource Ownership Verification

Ensure users can only access their own resources:

import { Hono } from 'hono';
import { uvbAuth, uvbRequire, uvbRequireOwnership } from '@sp-uvb/hono';

// Mock database
const posts = new Map([
  ['post_1', { id: 'post_1', title: 'Hello', authorId: 'user_123' }],
  ['post_2', { id: 'post_2', title: 'World', authorId: 'user_456' }],
]);

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
    required: true,
  })
);

app.delete(
  '/posts/:id',
  uvbRequireOwnership({
    getUserId: async (c) => {
      const postId = c.req.param('id');
      const post = posts.get(postId);
      if (!post) throw new Error('Post not found');
      return post.authorId;
    },
  }),
  (c) => {
    const postId = c.req.param('id');
    posts.delete(postId);
    return c.json({ deleted: postId });
  }
);

export default app;

Session Management Routes

Add built-in session management endpoints:

import { Hono } from 'hono';
import { uvbAuth, uvbSessionRoutes } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

app.route(
  '/uvb',
  uvbSessionRoutes({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);
// Adds:
// GET  /uvb/session - Get current session info
// POST /uvb/logout  - Revoke session and clear cookie

export default app;

Now you can:

# Get session info
curl http://localhost:8787/uvb/session \
  -H "Authorization: Bearer <session_token>"

# Logout
curl -X POST http://localhost:8787/uvb/logout \
  -H "Authorization: Bearer <session_token>"

Runtime-Specific Examples

Cloudflare Workers

import { Hono } from 'hono';
import { uvbAuth } from '@sp-uvb/hono';

type Bindings = {
  UVB_URL: string;
  UVB_TENANT_ID: string;
};

const app = new Hono<{ Bindings: Bindings }>();

app.use(
  '*',
  uvbAuth({
    uvbUrl: '', // Will be overridden by env
    tenantId: '', // Will be overridden by env
    getEnv: (c) => ({
      UVB_URL: c.env.UVB_URL,
      UVB_TENANT_ID: c.env.UVB_TENANT_ID,
    }),
  })
);

app.get('/api/user', (c) => {
  const session = c.get('uvbSession');
  return c.json({
    authenticated: !!session,
    userId: session?.userId,
  });
});

export default app;

Bun

import { Hono } from 'hono';
import { uvbAuth, uvbRequire } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

app.get('/dashboard', uvbRequire(), (c) => {
  const session = c.get('uvbSession')!;
  return c.json({ userId: session.userId });
});

export default {
  port: 3000,
  fetch: app.fetch,
};

Deno

import { Hono } from 'hono';
import { uvbAuth } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: Deno.env.get('UVB_URL')!,
    tenantId: Deno.env.get('UVB_TENANT_ID')!,
  })
);

app.get('/', (c) => c.text('Hello from Deno!'));

Deno.serve(app.fetch);

Node.js

import { serve } from '@hono/node-server';
import { Hono } from 'hono';
import { uvbAuth } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

app.get('/', (c) => c.text('Hello from Node!'));

serve(app);

Advanced Examples

Conditional MFA Requirements

import { Hono } from 'hono';
import { uvbAuth, uvbRequire, hasAllFactors } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
    required: true,
  })
);

app.post('/api/transactions', uvbRequire(), async (c) => {
  const session = c.get('uvbSession')!;
  const body = await c.req.json();
  const amount = parseFloat(body.amount);

  // Require MFA for large transactions
  if (amount > 10000 && !hasAllFactors(session, ['totp', 'webauthn'])) {
    return c.json(
      {
        error: 'MFA required for large transactions',
        required: ['totp', 'webauthn'],
        verified: session.factorsVerified,
      },
      403
    );
  }

  return c.json({
    transaction: 'processed',
    amount,
    userId: session.userId,
  });
});

export default app;

Custom Error Handlers

import { Hono } from 'hono';
import { uvbAuth } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
    required: true,
    onUnauthorized: (c) => {
      return c.json(
        {
          error: 'Authentication required',
          message: 'Please log in to continue',
          loginUrl: '/auth/login',
        },
        401
      );
    },
    onError: (c, error) => {
      console.error('UVB error:', error);
      return c.json(
        {
          error: 'Internal authentication error',
          requestId: crypto.randomUUID(),
        },
        500
      );
    },
  })
);

export default app;

Path Exclusions

import { Hono } from 'hono';
import { uvbAuth } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
    required: true,
    excludePaths: ['/health', '/metrics', '/public', '/auth'],
  })
);

app.get('/health', (c) => c.json({ status: 'ok' }));
app.get('/public/terms', (c) => c.text('Terms of Service...'));
app.get('/dashboard', (c) => {
  const session = c.get('uvbSession')!;
  return c.json({ userId: session.userId });
});

export default app;

Multiple Authentication Schemes

import { Hono } from 'hono';
import { uvbAuth } from '@sp-uvb/hono';

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

app.get('/api/data', (c) => {
  // Try UVB session first
  const session = c.get('uvbSession');
  if (session) {
    return c.json({
      data: 'authenticated via UVB',
      userId: session.userId,
    });
  }

  // Fallback to API key
  const apiKey = c.req.header('x-api-key');
  if (apiKey && isValidApiKey(apiKey)) {
    return c.json({
      data: 'authenticated via API key',
      apiKey: apiKey.substring(0, 8) + '...',
    });
  }

  return c.json({ error: 'Authentication required' }, 401);
});

function isValidApiKey(key: string): boolean {
  // Your API key validation logic
  return key.startsWith('sk_');
}

export default app;

Real-World Patterns

E-commerce API

import { Hono } from 'hono';
import { uvbAuth, uvbRequire, uvbRequireAllFactors, uvbRequireOwnership } from '@sp-uvb/hono';

// Mock database
const orders = new Map<string, { id: string; userId: string; total: number }>();

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

// Public product listing
app.get('/products', (c) => {
  return c.json([
    { id: 'prod_1', name: 'Widget', price: 29.99 },
    { id: 'prod_2', name: 'Gadget', price: 49.99 },
  ]);
});

// Cart requires authentication
app.post('/cart', uvbRequire(), async (c) => {
  const session = c.get('uvbSession')!;
  const body = await c.req.json();
  return c.json({
    cart: body,
    userId: session.userId,
  });
});

// Checkout requires MFA
app.post('/checkout', uvbRequireAllFactors(['password', 'totp']), async (c) => {
  const session = c.get('uvbSession')!;
  const body = await c.req.json();
  const orderId = crypto.randomUUID();
  orders.set(orderId, {
    id: orderId,
    userId: session.userId,
    total: body.total,
  });
  return c.json({ orderId, status: 'confirmed' });
});

// View order (must be owner)
app.get(
  '/orders/:id',
  uvbRequire(),
  uvbRequireOwnership({
    getUserId: async (c) => {
      const orderId = c.req.param('id');
      const order = orders.get(orderId);
      if (!order) throw new Error('Order not found');
      return order.userId;
    },
  }),
  (c) => {
    const orderId = c.req.param('id');
    const order = orders.get(orderId);
    return c.json(order);
  }
);

export default app;

Multi-Tenant SaaS

import { Hono } from 'hono';
import { uvbAuth, uvbRequire } from '@sp-uvb/hono';

interface User {
  id: string;
  tenantId: string;
  role: 'admin' | 'member';
}

const users = new Map<string, User>([
  ['user_1', { id: 'user_1', tenantId: 'tenant_123', role: 'admin' }],
  ['user_2', { id: 'user_2', tenantId: 'tenant_123', role: 'member' }],
  ['user_3', { id: 'user_3', tenantId: 'tenant_456', role: 'admin' }],
]);

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
    required: true,
  })
);

// Tenant-scoped data access
app.get('/api/workspace/:workspaceId', uvbRequire(), (c) => {
  const session = c.get('uvbSession')!;
  const user = users.get(session.userId);

  if (!user) {
    return c.json({ error: 'User not found' }, 404);
  }

  // Verify tenant access
  if (user.tenantId !== session.tenantId) {
    return c.json({ error: 'Forbidden' }, 403);
  }

  return c.json({
    workspaceId: c.req.param('workspaceId'),
    tenantId: user.tenantId,
    role: user.role,
  });
});

// Admin-only endpoint
app.post('/api/workspace/:workspaceId/settings', uvbRequire(), async (c) => {
  const session = c.get('uvbSession')!;
  const user = users.get(session.userId);

  if (!user || user.role !== 'admin') {
    return c.json({ error: 'Admin access required' }, 403);
  }

  return c.json({
    updated: true,
    workspaceId: c.req.param('workspaceId'),
  });
});

export default app;

Social Media API

import { Hono } from 'hono';
import { uvbAuth, uvbRequire, uvbRequireOwnership, uvbRequireAllFactors } from '@sp-uvb/hono';

interface Post {
  id: string;
  authorId: string;
  content: string;
  visibility: 'public' | 'private';
}

const posts = new Map<string, Post>();

const app = new Hono();

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

// Public feed (no auth required)
app.get('/feed', (c) => {
  return c.json(
    Array.from(posts.values())
      .filter((p) => p.visibility === 'public')
      .slice(0, 20)
  );
});

// Create post (auth required)
app.post('/posts', uvbRequire(), async (c) => {
  const session = c.get('uvbSession')!;
  const body = await c.req.json();
  const postId = crypto.randomUUID();
  const post: Post = {
    id: postId,
    authorId: session.userId,
    content: body.content,
    visibility: body.visibility || 'public',
  };
  posts.set(postId, post);
  return c.json(post);
});

// Edit post (must be author)
app.patch(
  '/posts/:id',
  uvbRequireOwnership({
    getUserId: async (c) => {
      const postId = c.req.param('id');
      const post = posts.get(postId);
      if (!post) throw new Error('Post not found');
      return post.authorId;
    },
  }),
  async (c) => {
    const postId = c.req.param('id');
    const body = await c.req.json();
    const post = posts.get(postId)!;
    post.content = body.content;
    return c.json(post);
  }
);

// Delete post (must be author + MFA)
app.delete(
  '/posts/:id',
  uvbRequireAllFactors(['password', 'totp']),
  uvbRequireOwnership({
    getUserId: async (c) => {
      const postId = c.req.param('id');
      const post = posts.get(postId);
      if (!post) throw new Error('Post not found');
      return post.authorId;
    },
  }),
  (c) => {
    const postId = c.req.param('id');
    posts.delete(postId);
    return c.json({ deleted: postId });
  }
);

export default app;

Helper Functions

Check Individual Factor

import { hasFactor } from '@sp-uvb/hono';

app.get('/settings/mfa', (c) => {
  const session = c.get('uvbSession');
  return c.json({
    totpEnabled: hasFactor(session, 'totp'),
    webauthnEnabled: hasFactor(session, 'webauthn'),
    smsEnabled: hasFactor(session, 'sms'),
  });
});

Check All Factors

import { hasAllFactors } from '@sp-uvb/hono';

app.get('/admin/panel', (c) => {
  const session = c.get('uvbSession');
  if (!hasAllFactors(session, ['password', 'totp', 'webauthn'])) {
    return c.text('Admin requires full MFA', 403);
  }
  return c.json({ admin: 'panel' });
});

Check Any Factor

import { hasAnyFactor } from '@sp-uvb/hono';

app.get('/settings', (c) => {
  const session = c.get('uvbSession');
  const hasMFA = hasAnyFactor(session, ['totp', 'webauthn', 'sms']);
  return c.json({
    mfaEnabled: hasMFA,
    message: hasMFA ? 'MFA is active' : 'Enable MFA for better security',
  });
});

API Reference

Middleware

uvbAuth(options: UvbMiddlewareOptions)

Main authentication middleware. Validates sessions and attaches to context.

uvbRequire()

Require authentication for a route. Returns 401 if not authenticated.

uvbRequireFactors(options: UvbFactorOptions)

Require specific authentication factors. Returns 403 if factors not verified.

uvbRequireAllFactors(factors: string[])

Require all specified factors. Returns 403 if any factor missing.

uvbRequireAnyFactor(factors: string[])

Require at least one of the specified factors. Returns 403 if no factors match.

uvbRequireOwnership(options: UvbOwnershipOptions)

Verify user owns a resource. Returns 403 if not owner.

Utilities

uvbSessionRoutes(options)

Create session management routes (GET /session, POST /logout).

hasFactor(session, factor): boolean

Check if session has specific factor.

hasAllFactors(session, factors): boolean

Check if session has all specified factors.

hasAnyFactor(session, factors): boolean

Check if session has any of the specified factors.

Best Practices

1. Environment Variables

Always use environment variables for sensitive configuration:

app.use(
  '*',
  uvbAuth({
    uvbUrl: process.env.UVB_URL!,
    tenantId: process.env.UVB_TENANT_ID!,
  })
);

2. Path Exclusions

Exclude health checks and public endpoints:

app.use(
  '*',
  uvbAuth({
    // ...
    excludePaths: ['/health', '/metrics', '/public'],
  })
);

3. Cloudflare Workers

Use getEnv for environment bindings:

app.use(
  '*',
  uvbAuth({
    uvbUrl: '',
    tenantId: '',
    getEnv: (c) => ({
      UVB_URL: c.env.UVB_URL,
      UVB_TENANT_ID: c.env.UVB_TENANT_ID,
    }),
  })
);

4. Factor Requirements

Use appropriate MFA levels for sensitive operations:

// Low risk: basic auth
app.get('/profile', uvbRequire(), handler);

// Medium risk: any MFA
app.post('/settings', uvbRequireAnyFactor(['totp', 'webauthn']), handler);

// High risk: all factors
app.post('/delete-account', uvbRequireAllFactors(['password', 'totp', 'webauthn']), handler);

Troubleshooting

Session Not Found

Problem: uvbSession is always null

Solutions:

  • Check UVB server is running and accessible
  • Verify uvbUrl and tenantId are correct
  • Ensure session token is being sent (cookie or header)
  • Check session hasn't expired

401 Errors

Problem: All routes return 401

Solutions:

  • Set required: false for optional auth
  • Add public paths to excludePaths
  • Verify session token is valid

403 Errors

Problem: Factor requirements failing

Solutions:

  • Check user has completed required MFA factors
  • Use hasAllFactors() to debug which factors are verified
  • Consider using uvbRequireAnyFactor instead

Cloudflare Workers

Problem: Environment variables not accessible

Solutions:

  • Use getEnv option to access c.env
  • Define bindings in wrangler.toml
  • Use type-safe environment bindings

License

MIT

Support

For issues and questions: