Node.js/TypeScript Guide
Node.js/TypeScript Implementation Guide
This guide covers the complete Node.js/TypeScript implementation of Veridot using the dverify
package.
Table of Contents
- Installation
- Environment Configuration
- Basic Usage
- Advanced Usage
- TypeScript Integration
- Production Deployment
- Performance Optimization
Installation
npm
npm install dverify
Yarn
yarn add dverify
pnpm
pnpm add dverify
Environment Configuration
Create a .env
file in your project root:
# Required: Kafka configuration
KAFKA_BROKER=localhost:9092
DVERIFY_KAFKA_TOPIC=veridot_public_keys
# Optional: Advanced configuration
DVERIFY_DB_PATH=./data/veridot # LMDB storage path
DVERIFY_KEY_ROTATION_MS=3600000 # 1 hour key rotation
DVERIFY_CLEANUP_INTERVAL_MS=1800000 # 30 min cleanup interval
Configuration Options
Variable | Description | Default |
---|---|---|
KAFKA_BROKER |
Kafka broker URL | localhost:9093 |
DVERIFY_KAFKA_TOPIC |
Topic for key exchange | public_keys_topic |
DVERIFY_DB_PATH |
LMDB storage path | ./signer-db |
DVERIFY_KEY_ROTATION_MS |
Key rotation interval | 3600000 (1h) |
DVERIFY_CLEANUP_INTERVAL_MS |
Expired key cleanup | 1800000 (30min) |
Basic Usage
Simple Mode (Recommended)
The DVerify
class provides a unified interface for both signing and verification:
import { DVerify } from 'dverify';
// Initialize (reads from environment variables)
const dverify = new DVerify();
// Sign data
const userData = {
userId: '12345',
email: 'user@example.com',
roles: ['user', 'premium']
};
const { token } = await dverify.sign(userData, 3600); // 1 hour validity
console.log('Token:', token);
// Verify token
const result = await dverify.verify<typeof userData>(token);
if (result.valid) {
console.log('User ID:', result.data.userId);
console.log('Email:', result.data.email);
console.log('Roles:', result.data.roles);
} else {
console.log('Invalid token');
}
Advanced Mode (Microservices)
For microservice architectures where signing and verification happen in different services:
import { DataSigner, DataVerifier } from 'dverify';
// Service A: Token Signing
const signer = new DataSigner();
const orderData = {
orderId: 'ORD-12345',
customerId: 'CUST-789',
items: [
{ sku: 'PROD-001', quantity: 2, price: 29.99 },
{ sku: 'PROD-002', quantity: 1, price: 39.99 }
],
total: 99.97,
createdAt: new Date().toISOString()
};
const token = await signer.sign(orderData, 1800); // 30 minutes
// Service B: Token Verification
const verifier = new DataVerifier();
try {
const order = await verifier.verify<OrderData>(token);
console.log(`Processing order ${order.orderId} for customer ${order.customerId}`);
// Process the order...
for (const item of order.items) {
console.log(`- ${item.sku}: ${item.quantity} x $${item.price}`);
}
} catch (error) {
console.error('Invalid order token:', error.message);
// Handle invalid token...
}
Advanced Usage
Custom Configuration
import { DataSigner, DataVerifier } from 'dverify';
// Custom configuration for specific environments
const signer = new DataSigner(
'kafka-cluster:9092', // broker
'custom_topic', // topic
'./custom/db/path', // dbPath
1800000 // rotation interval (30 min)
);
const verifier = new DataVerifier(
'kafka-cluster:9092', // broker
'custom_topic', // topic
'./custom/db/path', // dbPath
900000 // cleanup interval (15 min)
);
Error Handling
import { DVerify } from 'dverify';
import { JsonEncodingException } from 'dverify/exceptions';
const dverify = new DVerify();
// Comprehensive error handling for signing
async function signUserSession(user: User): Promise<string | null> {
try {
const sessionData = {
userId: user.id,
username: user.username,
permissions: user.permissions,
sessionId: crypto.randomUUID(),
createdAt: Date.now()
};
const { token } = await dverify.sign(sessionData, 7200); // 2 hours
return token;
} catch (error) {
if (error instanceof JsonEncodingException) {
console.error('Failed to encode user data:', error.message);
} else {
console.error('Token signing failed:', error.message);
}
return null;
}
}
// Comprehensive error handling for verification
async function verifyUserSession(token: string): Promise<UserSession | null> {
try {
const result = await dverify.verify<UserSession>(token);
if (result.valid) {
// Additional validation
if (result.data.createdAt + (7200 * 1000) < Date.now()) {
console.warn('Session expired');
return null;
}
return result.data;
}
return null;
} catch (error) {
console.error('Token verification failed:', error.message);
return null;
}
}
Batch Operations
import { DataSigner } from 'dverify';
const signer = new DataSigner();
// Sign multiple items efficiently
async function signBatch<T>(items: T[], duration: number): Promise<string[]> {
const tokens = await Promise.all(
items.map(item => signer.sign(item, duration))
);
console.log(`Signed ${tokens.length} tokens`);
return tokens;
}
// Usage
const orders = [
{ orderId: 'ORD-001', total: 99.99 },
{ orderId: 'ORD-002', total: 149.99 },
{ orderId: 'ORD-003', total: 79.99 }
];
const tokens = await signBatch(orders, 3600);
TypeScript Integration
Type Definitions
// types/user.ts
export interface User {
id: string;
email: string;
username: string;
roles: string[];
createdAt: string;
}
export interface UserSession {
userId: string;
sessionId: string;
permissions: string[];
expiresAt: number;
}
export interface OrderData {
orderId: string;
customerId: string;
items: OrderItem[];
total: number;
createdAt: string;
}
export interface OrderItem {
sku: string;
quantity: number;
price: number;
}
Service Class Implementation
// services/TokenService.ts
import { DVerify } from 'dverify';
import { User, UserSession } from '../types/user';
export class TokenService {
private readonly dverify: DVerify;
constructor() {
this.dverify = new DVerify();
}
async createUserSession(user: User, durationSeconds = 3600): Promise<string> {
const sessionData: UserSession = {
userId: user.id,
sessionId: crypto.randomUUID(),
permissions: this.calculatePermissions(user.roles),
expiresAt: Date.now() + (durationSeconds * 1000)
};
const { token } = await this.dverify.sign(sessionData, durationSeconds);
return token;
}
async verifyUserSession(token: string): Promise<UserSession | null> {
const result = await this.dverify.verify<UserSession>(token);
if (!result.valid) {
return null;
}
// Additional expiration check
if (result.data.expiresAt < Date.now()) {
console.warn('Session expired for user:', result.data.userId);
return null;
}
return result.data;
}
private calculatePermissions(roles: string[]): string[] {
const permissions: string[] = [];
if (roles.includes('admin')) {
permissions.push('READ', 'WRITE', 'DELETE', 'ADMIN');
} else if (roles.includes('editor')) {
permissions.push('READ', 'WRITE');
} else {
permissions.push('READ');
}
return permissions;
}
}
Express.js Middleware
// middleware/authMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { TokenService } from '../services/TokenService';
import { UserSession } from '../types/user';
declare global {
namespace Express {
interface Request {
userSession?: UserSession;
}
}
}
const tokenService = new TokenService();
export async function authenticateToken(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
const authHeader = req.headers.authorization;
const token = authHeader?.split(' ')[1]; // Bearer TOKEN
if (!token) {
res.status(401).json({ error: 'Access token required' });
return;
}
try {
const session = await tokenService.verifyUserSession(token);
if (!session) {
res.status(401).json({ error: 'Invalid or expired token' });
return;
}
req.userSession = session;
next();
} catch (error) {
console.error('Authentication error:', error);
res.status(500).json({ error: 'Authentication failed' });
}
}
// Permission-based authorization
export function requirePermission(permission: string) {
return (req: Request, res: Response, next: NextFunction): void => {
if (!req.userSession) {
res.status(401).json({ error: 'Not authenticated' });
return;
}
if (!req.userSession.permissions.includes(permission)) {
res.status(403).json({ error: 'Insufficient permissions' });
return;
}
next();
};
}
Usage in Express App
// app.ts
import express from 'express';
import { authenticateToken, requirePermission } from './middleware/authMiddleware';
import { TokenService } from './services/TokenService';
const app = express();
const tokenService = new TokenService();
app.use(express.json());
// Login endpoint
app.post('/auth/login', async (req, res) => {
try {
// Validate user credentials (implement your logic)
const user = await validateCredentials(req.body.email, req.body.password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = await tokenService.createUserSession(user, 3600);
res.json({
token,
user: {
id: user.id,
email: user.email,
roles: user.roles
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Login failed' });
}
});
// Protected routes
app.get('/api/profile', authenticateToken, (req, res) => {
res.json({
userId: req.userSession!.userId,
sessionId: req.userSession!.sessionId,
permissions: req.userSession!.permissions
});
});
app.delete('/api/admin/users/:id',
authenticateToken,
requirePermission('ADMIN'),
(req, res) => {
// Admin-only functionality
res.json({ message: 'User deleted' });
}
);
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Production Deployment
Docker Configuration
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm ci --only=production
# Copy application
COPY . .
# Build TypeScript
RUN npm run build
# Create directory for LMDB
RUN mkdir -p /app/data/veridot
# Set environment
ENV NODE_ENV=production
ENV DVERIFY_DB_PATH=/app/data/veridot
EXPOSE 3000
CMD ["npm", "start"]
Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
build: .
environment:
KAFKA_BROKER: kafka:9092
DVERIFY_KAFKA_TOPIC: veridot_keys
DVERIFY_DB_PATH: /app/data/veridot
NODE_ENV: production
volumes:
- veridot_data:/app/data
depends_on:
- kafka
ports:
- "3000:3000"
kafka:
image: confluentinc/cp-kafka:latest
environment:
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
depends_on:
- zookeeper
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
volumes:
veridot_data:
Health Checks
// health.ts
import { DVerify } from 'dverify';
export class HealthCheck {
private dverify: DVerify;
constructor() {
this.dverify = new DVerify();
}
async checkHealth(): Promise<{ status: string; details: any }> {
try {
// Test token signing and verification
const testData = { test: true, timestamp: Date.now() };
const { token } = await this.dverify.sign(testData, 60);
const result = await this.dverify.verify(token);
if (result.valid) {
return {
status: 'healthy',
details: {
veridot: 'operational',
lastCheck: new Date().toISOString()
}
};
} else {
throw new Error('Token verification failed');
}
} catch (error) {
return {
status: 'unhealthy',
details: {
veridot: 'failed',
error: error.message,
lastCheck: new Date().toISOString()
}
};
}
}
}
Performance Optimization
Connection Pooling
// Implement singleton pattern for better resource management
class VeridotManager {
private static instance: DVerify;
static getInstance(): DVerify {
if (!this.instance) {
this.instance = new DVerify();
}
return this.instance;
}
}
export const dverify = VeridotManager.getInstance();
Caching Strategy
import NodeCache from 'node-cache';
class CachedTokenService {
private cache = new NodeCache({ stdTTL: 300 }); // 5 minutes
private dverify = new DVerify();
async verifyWithCache(token: string): Promise<any> {
// Check cache first
const cached = this.cache.get(token);
if (cached) {
return cached;
}
// Verify and cache result
const result = await this.dverify.verify(token);
if (result.valid) {
this.cache.set(token, result.data);
}
return result.data;
}
}
Next Steps
- Explore Java implementation
- Review Security best practices
- Check API Reference
- See Deployment examples