Examples
Express.js Middleware Integration
Complete Express middleware setup for verifying agent requests with scope-based authorization
Express.js Middleware Integration
This example demonstrates how to integrate Beltic agent verification into an Express.js application with scope-based authorization.
Installation
npm install express @belticlabs/kyaBasic Middleware Setup
import express from 'express';
import { verifyAgentRequest } from '@belticlabs/kya';
const app = express();
app.use(express.json());
// Verification middleware factory
function requireAgent(options?: { scopes?: string[] }) {
return async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const result = await verifyAgentRequest(req, {
requiredScopes: options?.scopes,
});
if (!result.verified) {
return res.status(401).json({
error: 'Unauthorized',
code: result.error?.code,
message: result.error?.message,
});
}
// Attach verified context to request
req.agent = result.agent;
req.developer = result.developer;
next();
};
}
// Extend Express Request type
declare global {
namespace Express {
interface Request {
agent?: {
id: string;
name: string;
scopes: string[];
};
developer?: {
id: string;
legalName: string;
kybTier: string;
};
}
}
}Scope-Based Authorization
Defining Scopes
Structure scopes hierarchically for flexible access control:
// Scope hierarchy
const SCOPES = {
// Read access
'data:read': 'Read data',
'users:read': 'Read user information',
'payments:read': 'Read payment history',
// Write access
'data:write': 'Write data',
'users:write': 'Modify user information',
'payments:write': 'Initiate payments',
// Admin access
'admin:*': 'Full admin access',
} as const;Route Protection
// Public endpoint - no auth required
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// Read-only endpoint
app.get('/api/users/:id', requireAgent({ scopes: ['users:read'] }), (req, res) => {
const { agent, developer } = req;
console.log(`Agent ${agent?.id} from ${developer?.legalName} accessing user data`);
res.json({
id: req.params.id,
name: 'John Doe',
email: 'john@example.com',
});
});
// Write endpoint - requires elevated scope
app.post('/api/users', requireAgent({ scopes: ['users:write'] }), (req, res) => {
const { name, email } = req.body;
res.status(201).json({
id: 'usr_new123',
name,
email,
createdBy: req.agent?.id,
});
});
// Payment endpoint - requires specific payment scope
app.post('/api/payments/transfer', requireAgent({ scopes: ['payments:write'] }), (req, res) => {
const { amount, recipient } = req.body;
// Log for audit
console.log(`Payment initiated by agent ${req.agent?.id}`);
console.log(`Developer: ${req.developer?.legalName} (${req.developer?.kybTier})`);
res.json({
transactionId: 'txn_abc123',
amount,
recipient,
status: 'pending',
});
});KYB Tier-Based Access
Restrict endpoints based on developer verification level:
function requireKybTier(minTier: 'tier_1' | 'tier_2' | 'tier_3') {
const tierLevels = { tier_1: 1, tier_2: 2, tier_3: 3 };
return (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const developerTier = req.developer?.kybTier;
if (!developerTier) {
return res.status(403).json({
error: 'Forbidden',
message: 'Developer verification required',
});
}
// Extract tier level (e.g., 'tier_2_enhanced' -> 'tier_2')
const tierMatch = developerTier.match(/tier_(\d)/);
const currentLevel = tierMatch ? parseInt(tierMatch[1]) : 0;
const requiredLevel = tierLevels[minTier];
if (currentLevel < requiredLevel) {
return res.status(403).json({
error: 'Forbidden',
message: `This endpoint requires ${minTier} verification or higher`,
currentTier: developerTier,
requiredTier: minTier,
});
}
next();
};
}
// High-value transactions require tier 2
app.post(
'/api/payments/high-value',
requireAgent({ scopes: ['payments:write'] }),
requireKybTier('tier_2'),
(req, res) => {
res.json({ status: 'approved' });
}
);Error Handling
Comprehensive error handling for verification failures:
import { VerificationErrorCode } from '@belticlabs/kya';
function handleVerificationError(
error: { code?: string; message?: string },
res: express.Response
) {
const errorResponses: Record<string, { status: number; message: string }> = {
SIGNATURE_INVALID: {
status: 401,
message: 'Request signature is invalid or has been tampered with',
},
SIGNATURE_EXPIRED: {
status: 401,
message: 'Request signature has expired. Signatures are valid for 5 minutes.',
},
KEY_NOT_FOUND: {
status: 401,
message: 'Signing key not found in key directory',
},
CREDENTIAL_EXPIRED: {
status: 401,
message: 'Agent credential has expired. Please renew your credential.',
},
SCOPE_INSUFFICIENT: {
status: 403,
message: 'Agent does not have required scopes for this operation',
},
DEVELOPER_SUSPENDED: {
status: 403,
message: 'Developer account has been suspended',
},
};
const errorInfo = errorResponses[error.code || ''] || {
status: 401,
message: error.message || 'Authentication failed',
};
return res.status(errorInfo.status).json({
error: 'Authentication Error',
code: error.code,
message: errorInfo.message,
});
}
// Enhanced middleware with detailed errors
function requireAgentWithDetailedErrors(options?: { scopes?: string[] }) {
return async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const result = await verifyAgentRequest(req, {
requiredScopes: options?.scopes,
});
if (!result.verified) {
return handleVerificationError(result.error || {}, res);
}
req.agent = result.agent;
req.developer = result.developer;
next();
};
}Audit Logging
Log all verified requests for compliance:
interface AuditLog {
timestamp: string;
agentId: string;
developerId: string;
developerName: string;
kybTier: string;
method: string;
path: string;
scopes: string[];
ip: string;
}
function auditLogger(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
if (req.agent && req.developer) {
const auditEntry: AuditLog = {
timestamp: new Date().toISOString(),
agentId: req.agent.id,
developerId: req.developer.id,
developerName: req.developer.legalName,
kybTier: req.developer.kybTier,
method: req.method,
path: req.path,
scopes: req.agent.scopes,
ip: req.ip || 'unknown',
};
// Log to your audit system
console.log('AUDIT:', JSON.stringify(auditEntry));
}
next();
}
// Apply to all protected routes
app.use('/api/*', auditLogger);Complete Server Example
import express from 'express';
import { verifyAgentRequest } from '@belticlabs/kya';
const app = express();
app.use(express.json());
// Type definitions
declare global {
namespace Express {
interface Request {
agent?: { id: string; name: string; scopes: string[] };
developer?: { id: string; legalName: string; kybTier: string };
}
}
}
// Middleware factory
function requireAgent(scopes?: string[]) {
return async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const result = await verifyAgentRequest(req, { requiredScopes: scopes });
if (!result.verified) {
return res.status(401).json({
error: 'Unauthorized',
code: result.error?.code,
message: result.error?.message,
});
}
req.agent = result.agent;
req.developer = result.developer;
next();
};
}
// Routes
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.get('/api/data', requireAgent(['data:read']), (req, res) => {
res.json({
data: 'sensitive information',
accessedBy: req.agent?.id,
});
});
app.post('/api/data', requireAgent(['data:write']), (req, res) => {
res.status(201).json({
created: true,
createdBy: req.agent?.id,
});
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});Testing Your Middleware
Use the Beltic CLI to generate test requests:
# Generate a signed test request
beltic sign-request \
--method POST \
--url http://localhost:3000/api/data \
--key agent-key.pem \
--body '{"test": true}'Or programmatically with the SDK:
import { signHttpRequest, importPrivateKey } from '@belticlabs/kya';
import fs from 'fs';
async function testEndpoint() {
const privateKey = await importPrivateKey(
fs.readFileSync('agent-key.pem', 'utf-8'),
'EdDSA'
);
const body = JSON.stringify({ test: true });
const headers = await signHttpRequest({
method: 'POST',
url: 'http://localhost:3000/api/data',
headers: { 'content-type': 'application/json' },
body,
privateKey,
keyId: process.env.AGENT_KEY_ID!,
keyDirectoryUrl: process.env.KEY_DIRECTORY_URL!,
credentialUrl: process.env.CREDENTIAL_URL!,
});
const response = await fetch('http://localhost:3000/api/data', {
method: 'POST',
headers: { ...headers, 'content-type': 'application/json' },
body,
});
console.log(await response.json());
}