Package Exports
- @sp-uvb/fastify
Readme
@sp-uvb/fastify
Fastify plugin for Universal Verification Broker (UVB) authentication.
Installation
npm install @sp-uvb/fastify
# or
yarn add @sp-uvb/fastify
# or
pnpm add @sp-uvb/fastifyQuick Start
import Fastify from 'fastify';
import uvbPlugin, { requireFactors } from '@sp-uvb/fastify';
const fastify = Fastify();
// Register UVB authentication plugin
await fastify.register(uvbPlugin, {
tenantId: 'my-tenant',
uvbUrl: 'http://localhost:8080',
excludePaths: ['/login', '/health', '/public'],
});
// Access the authenticated user
fastify.get('/profile', async (request, reply) => {
const session = request.uvbSession;
return {
userId: session?.userId,
factors: session?.factorsVerified,
};
});
// Require specific MFA factors for sensitive routes
fastify.post(
'/admin/delete',
{ preHandler: requireFactors(['totp', 'webauthn']) },
async (request, reply) => {
return { message: 'Admin action completed' };
}
);
await fastify.listen({ port: 3000 });API
Plugin Options
When registering the plugin, you can provide these options:
tenantId(required): Your UVB tenant IDuvbUrl(optional): UVB server URL, defaults tohttp://localhost:8080apiKey(optional): API key for server-to-server authenticationcookieName(optional): Cookie name for session token, defaults touvb_sessionexcludePaths(optional): Array of paths to exclude from authentication, defaults to[]onError(optional): Custom error handler function
Attached to Request:
After successful authentication, request.uvbSession contains:
interface UVBSession {
userId: string;
tenantId: string;
factorsVerified: string[];
sessionId: string;
expiresAt: Date;
}requireFactors(factors: string[])
Pre-handler to require specific MFA factors for a route.
fastify.get(
'/sensitive',
{ preHandler: requireFactors(['totp', 'webauthn']) },
async (request, reply) => {
// Only accessible if user has verified both TOTP and WebAuthn
return { message: 'Sensitive data' };
}
);getSession(request: FastifyRequest)
Helper function to get the current UVB session from a request.
import { getSession } from '@sp-uvb/fastify';
fastify.get('/info', async (request, reply) => {
const session = getSession(request);
if (!session) {
return reply.code(401).send({ error: 'Not authenticated' });
}
return { userId: session.userId };
});Examples
Protecting Specific Routes
import Fastify from 'fastify';
import uvbPlugin from '@sp-uvb/fastify';
const fastify = Fastify();
// Public routes
fastify.get('/health', async (request, reply) => {
return { status: 'ok' };
});
// Protected routes
await fastify.register(async (protectedRoutes) => {
await protectedRoutes.register(uvbPlugin, {
tenantId: 'my-tenant',
uvbUrl: process.env.UVB_URL,
});
protectedRoutes.get('/api/data', async (request, reply) => {
// User is authenticated
return { data: 'protected' };
});
});
await fastify.listen({ port: 3000 });Custom Error Handling
await fastify.register(uvbPlugin, {
tenantId: 'my-tenant',
onError: (err, request, reply) => {
fastify.log.error('UVB auth error:', err);
return reply.code(401).send({
error: 'Authentication failed',
details: err.message,
});
},
});Conditional MFA Requirements
import { requireFactors } from '@sp-uvb/fastify';
fastify.post('/transfer', async (request, reply) => {
const amount = request.body.amount;
// Require additional auth for large transfers
if (amount > 10000) {
await requireFactors(['totp', 'webauthn'])(request, reply);
}
return { message: 'Transfer initiated' };
});Multiple Route Groups
const fastify = Fastify();
// Public API
fastify.get('/public/info', async () => ({ info: 'public' }));
// Basic auth required
await fastify.register(async (basicRoutes) => {
await basicRoutes.register(uvbPlugin, { tenantId: 'my-tenant' });
basicRoutes.get('/user/profile', async (request) => ({
userId: request.uvbSession?.userId,
}));
});
// Admin routes with strict MFA
await fastify.register(async (adminRoutes) => {
await adminRoutes.register(uvbPlugin, { tenantId: 'my-tenant' });
adminRoutes.addHook('onRequest', requireFactors(['totp', 'webauthn']));
adminRoutes.delete('/admin/user/:id', async (request) => ({
message: 'User deleted',
}));
});TypeScript
This package includes TypeScript definitions. The FastifyRequest type is automatically extended:
import { FastifyRequest, FastifyReply } from 'fastify';
fastify.get('/test', async (request: FastifyRequest, reply: FastifyReply) => {
// TypeScript knows about uvbSession
const userId = request.uvbSession?.userId;
return { userId };
});License
MIT