@sp-uvb/elysia
v0.1.0
Published
Production-grade Elysia plugin for Universal Verification Broker (UVB) authentication
Maintainers
Readme
@sp-uvb/elysia
Production-grade Elysia plugin for Universal Verification Broker (UVB) authentication. Built using Elysia's Service Locator pattern for type-safe session management across your Bun application.
Features
- 🔐 Automatic Session Validation - Validates sessions on every request
- 🎯 Service Locator Pattern - Type-safe
uvbSessionaccess in all routes - 🛡️ MFA Factor Guards - Require specific authentication factors per route
- 👤 Resource Ownership - Verify users own resources they're accessing
- 🚀 Zero Configuration - Works out of the box with sensible defaults
- 📊 Built-in Session Routes - Optional
/uvb/sessionand/uvb/logoutendpoints - 🔧 Fully Customizable - Override error handlers, customize behavior
- 💪 TypeScript First - Complete type safety with Elysia's plugin system
Installation
bun add @sp-uvb/elysia elysiaQuick Start
Basic Setup
import { Elysia } from 'elysia';
import { uvb } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL || 'http://localhost:8080',
tenantId: process.env.UVB_TENANT_ID || 'tenant_123',
})
)
.get('/', () => 'Hello UVB!')
.get('/profile', ({ uvbSession }) => {
if (!uvbSession) {
return { error: 'Not authenticated' };
}
return {
userId: uvbSession.userId,
factors: uvbSession.factorsVerified,
};
})
.listen(3000);
console.log(`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`);Required Authentication
import { Elysia } from 'elysia';
import { uvb, uvbGuard } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
required: true, // All routes require authentication
})
)
.get('/dashboard', ({ uvbSession }) => {
// uvbSession is guaranteed to exist when required: true
return {
welcome: `Hello, user ${uvbSession.userId}!`,
sessionId: uvbSession.sessionId,
};
})
.listen(3000);Configuration Options
UvbPluginOptions
interface UvbPluginOptions {
// 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: Custom error handler
onError?: (context: Context, error: Error) => Response | Promise<Response>;
// Optional: Custom unauthorized handler
onUnauthorized?: (context: Context) => Response | Promise<Response>;
// Optional: Custom insufficient factors handler
onInsufficientFactors?: (
context: Context,
required: string[],
verified: string[]
) => Response | Promise<Response>;
}Session Object
The uvbSession object is automatically attached to your route context:
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 { Elysia } from 'elysia';
import { uvb, uvbGuard } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
})
)
.get('/public', () => 'Anyone can access')
.get(
'/protected',
({ uvbSession }) => {
return { userId: uvbSession!.userId };
},
{
beforeHandle: uvbGuard(),
}
)
.listen(3000);MFA Factor Requirements
Require All Factors
import { Elysia } from 'elysia';
import { uvb, uvbRequireAllFactors } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
})
)
.post(
'/transfer',
async ({ body, uvbSession }) => {
// User has verified password + TOTP + WebAuthn
return {
transfer: 'authorized',
amount: body.amount,
userId: uvbSession!.userId,
};
},
{
beforeHandle: uvbRequireAllFactors(['password', 'totp', 'webauthn']),
}
)
.listen(3000);Require Any Factor
import { Elysia } from 'elysia';
import { uvb, uvbRequireAnyFactor } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
})
)
.get(
'/settings',
({ uvbSession }) => {
return {
userId: uvbSession!.userId,
mfaEnabled: true,
};
},
{
beforeHandle: uvbRequireAnyFactor(['totp', 'webauthn', 'sms']),
}
)
.listen(3000);Resource Ownership Verification
Ensure users can only access their own resources:
import { Elysia } from 'elysia';
import { uvb, uvbRequireOwnership } from '@sp-uvb/elysia';
// 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 Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
required: true,
})
)
.delete(
'/posts/:id',
({ params }) => {
posts.delete(params.id);
return { deleted: params.id };
},
{
beforeHandle: uvbRequireOwnership({
getUserId: async ({ params }) => {
const post = posts.get(params.id);
if (!post) throw new Error('Post not found');
return post.authorId;
},
}),
}
)
.listen(3000);Session Management Routes
Add built-in session management endpoints:
import { Elysia } from 'elysia';
import { uvb, uvbSessionRoutes } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
})
)
.use(
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
.listen(3000);Now you can:
# Get session info
curl http://localhost:3000/uvb/session \
-H "Authorization: Bearer <session_token>"
# Logout
curl -X POST http://localhost:3000/uvb/logout \
-H "Authorization: Bearer <session_token>"Advanced Examples
Conditional MFA Requirements
import { Elysia } from 'elysia';
import { uvb, uvbGuard, hasAllFactors } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
required: true,
})
)
.post('/api/transactions', async ({ body, uvbSession }) => {
const amount = parseFloat(body.amount);
// Require MFA for large transactions
if (amount > 10000 && !hasAllFactors(uvbSession, ['totp', 'webauthn'])) {
return new Response(
JSON.stringify({
error: 'MFA required for large transactions',
required: ['totp', 'webauthn'],
verified: uvbSession!.factorsVerified,
}),
{
status: 403,
headers: { 'Content-Type': 'application/json' },
}
);
}
return {
transaction: 'processed',
amount,
userId: uvbSession!.userId,
};
})
.listen(3000);Custom Error Handlers
import { Elysia } from 'elysia';
import { uvb } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
required: true,
onUnauthorized: (context) => {
return new Response(
JSON.stringify({
error: 'Authentication required',
message: 'Please log in to continue',
loginUrl: '/auth/login',
}),
{
status: 401,
headers: {
'Content-Type': 'application/json',
'WWW-Authenticate': 'Bearer realm="UVB"',
},
}
);
},
onError: (context, error) => {
console.error('UVB error:', error);
return new Response(
JSON.stringify({
error: 'Internal authentication error',
requestId: crypto.randomUUID(),
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
},
})
)
.listen(3000);Path Exclusions
import { Elysia } from 'elysia';
import { uvb } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
required: true,
excludePaths: ['/health', '/metrics', '/public', '/auth'],
})
)
.get('/health', () => ({ status: 'ok' }))
.get('/public/terms', () => 'Terms of Service...')
.get('/dashboard', ({ uvbSession }) => {
return { userId: uvbSession!.userId };
})
.listen(3000);Multiple Authentication Schemes
import { Elysia } from 'elysia';
import { uvb } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
})
)
.get('/api/data', ({ uvbSession, headers }) => {
// Try UVB session first
if (uvbSession) {
return {
data: 'authenticated via UVB',
userId: uvbSession.userId,
};
}
// Fallback to API key
const apiKey = headers['x-api-key'];
if (apiKey && isValidApiKey(apiKey)) {
return {
data: 'authenticated via API key',
apiKey: apiKey.substring(0, 8) + '...',
};
}
return new Response(JSON.stringify({ error: 'Authentication required' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
})
.listen(3000);
function isValidApiKey(key: string): boolean {
// Your API key validation logic
return key.startsWith('sk_');
}WebSocket with Authentication
import { Elysia } from 'elysia';
import { uvb } from '@sp-uvb/elysia';
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
})
)
.ws('/ws', {
open(ws) {
const session = ws.data.uvbSession;
if (!session) {
ws.close(1008, 'Authentication required');
return;
}
console.log(`User ${session.userId} connected`);
},
message(ws, message) {
const session = ws.data.uvbSession;
ws.send({
echo: message,
userId: session?.userId,
timestamp: new Date().toISOString(),
});
},
close(ws) {
console.log('Client disconnected');
},
})
.listen(3000);Helper Functions
Check Individual Factor
import { hasFactor } from '@sp-uvb/elysia';
app.get('/settings/mfa', ({ uvbSession }) => {
return {
totpEnabled: hasFactor(uvbSession, 'totp'),
webauthnEnabled: hasFactor(uvbSession, 'webauthn'),
smsEnabled: hasFactor(uvbSession, 'sms'),
};
});Check All Factors
import { hasAllFactors } from '@sp-uvb/elysia';
app.get('/admin/panel', ({ uvbSession }) => {
if (!hasAllFactors(uvbSession, ['password', 'totp', 'webauthn'])) {
return new Response('Admin requires full MFA', { status: 403 });
}
return { admin: 'panel' };
});Check Any Factor
import { hasAnyFactor } from '@sp-uvb/elysia';
app.get('/settings', ({ uvbSession }) => {
const hasMFA = hasAnyFactor(uvbSession, ['totp', 'webauthn', 'sms']);
return {
mfaEnabled: hasMFA,
message: hasMFA ? 'MFA is active' : 'Enable MFA for better security',
};
});Real-World Patterns
E-commerce API
import { Elysia } from 'elysia';
import { uvb, uvbGuard, uvbRequireAllFactors, uvbRequireOwnership } from '@sp-uvb/elysia';
// Mock database
const orders = new Map<string, { id: string; userId: string; total: number }>();
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
})
)
// Public product listing
.get('/products', () => {
return [
{ id: 'prod_1', name: 'Widget', price: 29.99 },
{ id: 'prod_2', name: 'Gadget', price: 49.99 },
];
})
// Cart requires authentication
.post(
'/cart',
({ body, uvbSession }) => {
return {
cart: body,
userId: uvbSession!.userId,
};
},
{
beforeHandle: uvbGuard(),
}
)
// Checkout requires MFA
.post(
'/checkout',
async ({ body, uvbSession }) => {
const orderId = crypto.randomUUID();
orders.set(orderId, {
id: orderId,
userId: uvbSession!.userId,
total: body.total,
});
return { orderId, status: 'confirmed' };
},
{
beforeHandle: uvbRequireAllFactors(['password', 'totp']),
}
)
// View order (must be owner)
.get(
'/orders/:id',
({ params }) => {
const order = orders.get(params.id);
if (!order) {
return new Response('Order not found', { status: 404 });
}
return order;
},
{
beforeHandle: [
uvbGuard(),
uvbRequireOwnership({
getUserId: async ({ params }) => {
const order = orders.get(params.id);
if (!order) throw new Error('Order not found');
return order.userId;
},
}),
],
}
)
.listen(3000);Multi-Tenant SaaS
import { Elysia } from 'elysia';
import { uvb, uvbGuard } from '@sp-uvb/elysia';
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 Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
required: true,
})
)
.derive(({ uvbSession }) => {
const user = users.get(uvbSession!.userId);
return { user };
})
// Tenant-scoped data access
.get('/api/workspace/:workspaceId', ({ params, user, uvbSession }) => {
if (!user) {
return new Response('User not found', { status: 404 });
}
// Verify tenant access
if (user.tenantId !== uvbSession!.tenantId) {
return new Response('Forbidden', { status: 403 });
}
return {
workspaceId: params.workspaceId,
tenantId: user.tenantId,
role: user.role,
};
})
// Admin-only endpoint
.post('/api/workspace/:workspaceId/settings', ({ params, user }) => {
if (!user || user.role !== 'admin') {
return new Response('Admin access required', { status: 403 });
}
return {
updated: true,
workspaceId: params.workspaceId,
};
})
.listen(3000);Social Media API
import { Elysia } from 'elysia';
import { uvb, uvbGuard, uvbRequireOwnership, uvbRequireAllFactors } from '@sp-uvb/elysia';
interface Post {
id: string;
authorId: string;
content: string;
visibility: 'public' | 'private';
}
const posts = new Map<string, Post>();
const app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
})
)
// Public feed (no auth required)
.get('/feed', () => {
return Array.from(posts.values())
.filter((p) => p.visibility === 'public')
.slice(0, 20);
})
// Create post (auth required)
.post(
'/posts',
({ body, uvbSession }) => {
const postId = crypto.randomUUID();
const post: Post = {
id: postId,
authorId: uvbSession!.userId,
content: body.content,
visibility: body.visibility || 'public',
};
posts.set(postId, post);
return post;
},
{
beforeHandle: uvbGuard(),
}
)
// Edit post (must be author)
.patch(
'/posts/:id',
({ params, body }) => {
const post = posts.get(params.id);
if (!post) {
return new Response('Post not found', { status: 404 });
}
post.content = body.content;
return post;
},
{
beforeHandle: uvbRequireOwnership({
getUserId: async ({ params }) => {
const post = posts.get(params.id);
if (!post) throw new Error('Post not found');
return post.authorId;
},
}),
}
)
// Delete post (must be author + MFA)
.delete(
'/posts/:id',
({ params }) => {
posts.delete(params.id);
return { deleted: params.id };
},
{
beforeHandle: [
uvbRequireAllFactors(['password', 'totp']),
uvbRequireOwnership({
getUserId: async ({ params }) => {
const post = posts.get(params.id);
if (!post) throw new Error('Post not found');
return post.authorId;
},
}),
],
}
)
.listen(3000);Testing
Unit Testing
import { describe, expect, it } from 'bun:test';
import { Elysia } from 'elysia';
import { uvb, uvbGuard } from '@sp-uvb/elysia';
describe('UVB Authentication', () => {
it('should allow public access without auth', async () => {
const app = new Elysia()
.use(
uvb({
uvbUrl: 'http://localhost:8080',
tenantId: 'test_tenant',
})
)
.get('/public', () => 'public');
const response = await app.handle(new Request('http://localhost/public'));
expect(response.status).toBe(200);
expect(await response.text()).toBe('public');
});
it('should block protected routes without auth', async () => {
const app = new Elysia()
.use(
uvb({
uvbUrl: 'http://localhost:8080',
tenantId: 'test_tenant',
})
)
.get(
'/protected',
({ uvbSession }) => {
return { userId: uvbSession!.userId };
},
{
beforeHandle: uvbGuard(),
}
);
const response = await app.handle(new Request('http://localhost/protected'));
expect(response.status).toBe(401);
});
it('should allow access with valid session', async () => {
const app = new Elysia()
.use(
uvb({
uvbUrl: 'http://localhost:8080',
tenantId: 'test_tenant',
})
)
.get(
'/profile',
({ uvbSession }) => {
return { userId: uvbSession!.userId };
},
{
beforeHandle: uvbGuard(),
}
);
const response = await app.handle(
new Request('http://localhost/profile', {
headers: {
Authorization: 'Bearer valid_session_token',
},
})
);
expect(response.status).toBe(200);
const data = await response.json();
expect(data.userId).toBeDefined();
});
});Integration Testing
import { describe, expect, it, beforeAll, afterAll } from 'bun:test';
import { Elysia } from 'elysia';
import { uvb, uvbRequireAllFactors } from '@sp-uvb/elysia';
let app: Elysia;
let sessionToken: string;
beforeAll(async () => {
// Start app
app = new Elysia()
.use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
})
)
.get('/data', ({ uvbSession }) => ({ data: 'test' }), {
beforeHandle: uvbRequireAllFactors(['password', 'totp']),
})
.listen(3001);
// Create test session
const authResponse = await fetch(`${process.env.UVB_URL}/api/v1/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tenant_id: process.env.UVB_TENANT_ID,
username: '[email protected]',
password: 'password',
}),
});
const authData = await authResponse.json();
sessionToken = authData.session_token;
});
afterAll(() => {
app.stop();
});
describe('MFA Requirements', () => {
it('should block access without MFA', async () => {
const response = await fetch('http://localhost:3001/data', {
headers: { Authorization: `Bearer ${sessionToken}` },
});
expect(response.status).toBe(403);
});
it('should allow access with MFA', async () => {
// Complete MFA challenge
await fetch(`${process.env.UVB_URL}/api/v1/mfa/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_token: sessionToken,
factor: 'totp',
code: '123456',
}),
});
const response = await fetch('http://localhost:3001/data', {
headers: { Authorization: `Bearer ${sessionToken}` },
});
expect(response.status).toBe(200);
});
});API Reference
Main Plugin
uvb(options: UvbPluginOptions)
Main authentication plugin. Adds session validation and attaches uvbSession to context.
Guards
uvbGuard(options?: UvbGuardOptions)
Basic authentication guard. Returns 401 if not authenticated.
uvbRequireFactors(factors: string[], options?: UvbGuardOptions)
Require specific authentication factors. Returns 403 if factors not verified.
uvbRequireAllFactors(factors: string[], options?: UvbGuardOptions)
Require all specified factors. Returns 403 if any factor missing.
uvbRequireAnyFactor(factors: string[], options?: UvbGuardOptions)
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: { uvbUrl: string; tenantId: string })
Add session management routes:
GET /uvb/session- Get current sessionPOST /uvb/logout- Revoke session
hasFactor(session: UvbSession | null, factor: string): boolean
Check if session has specific factor.
hasAllFactors(session: UvbSession | null, factors: string[]): boolean
Check if session has all specified factors.
hasAnyFactor(session: UvbSession | null, factors: string[]): boolean
Check if session has any of the specified factors.
Best Practices
1. Environment Variables
Always use environment variables for sensitive configuration:
const app = new Elysia().use(
uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
})
);2. Path Exclusions
Exclude health checks and public endpoints:
.use(uvb({
// ...
excludePaths: ['/health', '/metrics', '/public'],
}))3. Custom Error Handlers
Provide user-friendly error messages:
.use(uvb({
// ...
onUnauthorized: () => new Response(
JSON.stringify({ error: 'Please log in' }),
{ status: 401, headers: { 'Content-Type': 'application/json' } }
),
}))4. Factor Requirements
Use appropriate MFA levels for sensitive operations:
// Low risk: basic auth
.get('/profile', handler, { beforeHandle: uvbGuard() })
// Medium risk: any MFA
.post('/settings', handler, {
beforeHandle: uvbRequireAnyFactor(['totp', 'webauthn'])
})
// High risk: all factors
.post('/delete-account', handler, {
beforeHandle: uvbRequireAllFactors(['password', 'totp', 'webauthn'])
})5. Session Expiration
Check session expiration in your application:
.get('/data', ({ uvbSession }) => {
if (uvbSession && uvbSession.expiresAt < new Date()) {
return new Response('Session expired', { status: 401 })
}
return { data: 'value' }
})Troubleshooting
Session Not Found
Problem: uvbSession is always null
Solutions:
- Check UVB server is running and accessible
- Verify
uvbUrlandtenantIdare 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: falsefor 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
uvbRequireAnyFactorinstead ofuvbRequireAllFactors
TypeScript Errors
Problem: uvbSession type errors
Solutions:
- Ensure
@sp-uvb/elysiais properly installed - Use guards to guarantee session exists
- Check for null:
if (!uvbSession) return ...
Migration Guide
From Custom Elysia Middleware
Before:
.derive(async ({ cookie }) => {
const token = cookie.session
const session = await validateWithUvb(token)
return { session }
})After:
.use(uvb({
uvbUrl: process.env.UVB_URL!,
tenantId: process.env.UVB_TENANT_ID!,
}))
// Access via context.uvbSessionFrom Manual Factor Checks
Before:
.post('/transfer', async ({ session }) => {
if (!session.factors.includes('totp')) {
throw new Error('TOTP required')
}
// ...
})After:
.post('/transfer', handler, {
beforeHandle: uvbRequireAllFactors(['totp'])
})License
MIT
Support
For issues and questions:
- GitHub: https://github.com/uvb/uvb
- Documentation: https://docs.uvb.dev
