@rodit/rodit-auth-be
v3.1.0
Published
RODiT-based authentication system for Express.js applications
Downloads
504
Maintainers
Readme
RODiT Authentication SDK
A comprehensive Node.js SDK for implementing RODiT-based mutual authentication, authorization, self-configuration, and session management in Express.js applications.
Version: 2.9.0
License: Proprietary
Author: Discernible Inc.
Table of Contents
- Quick Start
- Core Concepts
- Installation & Setup
- Authentication
- Authorization & Permissions
- Session Management
- Configuration
- Logging & Monitoring
- Performance Tracking
- Webhooks
- Advanced Usage
- API Reference
- Best Practices
- Troubleshooting
Quick Start
Installation
npm install @rodit/rodit-auth-beBasic Server Setup
const express = require('express');
const { RoditClient, setExpressSessionStore } = require('@rodit/rodit-auth-be');
const { ulid } = require('ulid');
const session = require('express-session');
const SQLiteStore = require('connect-sqlite3')(session);
const app = express();
let roditClient;
// Configure session storage BEFORE initializing RoditClient
const sessionStore = new SQLiteStore({
db: 'sessions.db',
dir: './data',
table: 'sessions'
});
setExpressSessionStore(sessionStore);
// Configure Express middleware
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// Request context middleware
app.use((req, res, next) => {
req.requestId = req.headers['x-request-id'] || ulid();
req.startTime = Date.now();
next();
});
// Server startup with SDK initialization
async function startServer() {
try {
// Initialize RODiT client (use 'server' for server applications)
roditClient = await RoditClient.create('server');
// Store client in app.locals for route access
app.locals.roditClient = roditClient;
// Get logger and other services from client
const logger = roditClient.getLogger();
const config = roditClient.getConfig();
const loggingmw = roditClient.getLoggingMiddleware();
// Apply logging middleware
app.use(loggingmw);
// Create authentication middleware
const authenticate = (req, res, next) => roditClient.authenticate(req, res, next);
const authorize = (req, res, next) => roditClient.authorize(req, res, next);
// Public routes
app.post('/api/login', (req, res) => {
req.logAction = 'login-attempt';
return roditClient.login_client(req, res);
});
// Protected routes
app.post('/api/logout', authenticate, (req, res) => {
req.logAction = 'logout-attempt';
return roditClient.logout_client(req, res);
});
app.get('/api/protected', authenticate, (req, res) => {
res.json({ message: 'Protected data', user: req.user });
});
// Protected + authorized routes
app.use('/api/admin', authenticate, authorize, adminRoutes);
const port = 3000;
app.listen(port, () => {
logger.info(`RODiT Authentication Server running on port ${port}`);
});
} catch (error) {
console.error('Server initialization failed:', error);
process.exit(1);
}
}
startServer();Core Concepts
The RoditClient Pattern
The SDK centers around the RoditClient class, which provides a unified interface for all RODiT operations:
- Single Initialization: Create once with
RoditClient.create(role)where role is'server','client', or'portal' - Shared Instance: Store in
app.localsfor access across routes and middleware - Self-Configuring: Automatically loads configuration from Vault, files, or environment variables
- Encapsulated: All SDK functionality accessed through the client instance
- Session Management: Built-in session tracking with pluggable storage backends
- Performance Monitoring: Integrated request tracking and metrics collection
App.locals Pattern
Store the initialized client in app.locals for consistent access across your application:
// In main app.js
roditClient = await RoditClient.create('server');
app.locals.roditClient = roditClient;
// In route modules
const router = express.Router();
router.get('/data', (req, res) => {
const client = req.app.locals.roditClient;
const logger = client.getLogger();
logger.info('Processing request', {
component: 'DataRoute',
userId: req.user?.id
});
res.json({ data: 'example' });
});Authentication Middleware Pattern
Create middleware functions that delegate to the RoditClient:
// Create reusable middleware
const authenticate = (req, res, next) => {
const client = req.app.locals.roditClient;
if (!client) {
return res.status(503).json({ error: 'Authentication service unavailable' });
}
return client.authenticate(req, res, next);
};
const authorize = (req, res, next) => {
const client = req.app.locals.roditClient;
if (!client) {
return res.status(503).json({ error: 'Authorization service unavailable' });
}
return client.authorize(req, res, next);
};
// Use in routes
app.get('/api/protected', authenticate, handler);
app.post('/api/admin', authenticate, authorize, adminHandler);Installation & Setup
Dependencies
Required:
npm install @rodit/rodit-auth-be express config winstonRecommended for Production:
npm install express-session connect-sqlite3Optional:
npm install node-vault # For Vault-based credentials
npm install winston-loki # For Grafana Loki loggingEnvironment Variables
Vault Configuration (Production):
export RODIT_NEAR_CREDENTIALS_SOURCE=vault
export VAULT_ENDPOINT=https://vault.example.com
export VAULT_ROLE_ID=your-role-id
export VAULT_SECRET_ID=your-secret-id
export VAULT_RODIT_KEYVALUE_PATH=secret/rodit
export SERVICE_NAME=your-service-name
export NEAR_CONTRACT_ID=your-contract.testnetApplication Configuration:
export NODE_ENV=production # Environment: production, development, test
export LOG_LEVEL=info # Logging: error, warn, info, debug, trace
export API_DEFAULT_OPTIONS_DB_PATH=/app/data/database.sqliteSession Configuration:
export SESSION_STORAGE_TYPE=express-session # Storage: memory, express, express-session
export SESSION_CLEANUP_INTERVAL=3600000 # Cleanup interval in milliseconds (1 hour)
export SESSION_TOKEN_RETENTION_PERIOD=604800 # Token retention in seconds (7 days)
export SESSION_VALIDATION_CACHE_TTL=5000 # Cache TTL in milliseconds (5 seconds)Logging Configuration:
export LOKI_URL=https://loki.example.com:3100
export LOKI_BASIC_AUTH=username:passwordConfiguration Files
Create config/default.json:
{
"NEAR_CONTRACT_ID": "your-contract.testnet",
"SERVICE_NAME": "your-service",
"SECURITY_OPTIONS": {
"SILENT_LOGIN_FAILURES": false,
"JWT_DURATION": 3600
}
}Authentication
RODiT-Based Authentication
RODiT provides cryptographic mutual authentication using blockchain-verified identities.
Client Login Request
Clients authenticate by sending RODiT credentials:
// POST /api/login
{
"roditid": "01K4G3D95QF6NR0RSJK9WEK6KA",
"timestamp": 1640995200,
"roditid_base64url_signature": "base64url-encoded-signature"
}Server Response
// Success (200)
{
"message": "Login successful",
"token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
"requestId": "01HQXYZ123ABC"
}
// Headers:
// Authorization: Bearer eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...Authentication Flow
- Client sends RODiT credentials - RODiT ID, timestamp, and cryptographic signature
- SDK verifies signature - Validates against blockchain records (NEAR Protocol)
- Session created - New session stored in session manager
- JWT token issued - Token contains session ID and user claims
- Subsequent requests - Client sends JWT in
Authorization: Bearer <token>header - Token validation - SDK validates JWT and checks session status
Login Implementation
// routes/login.js
const express = require('express');
const router = express.Router();
router.post('/login', async (req, res) => {
req.logAction = 'login-attempt';
const client = req.app.locals.roditClient;
if (!client) {
return res.status(503).json({ error: 'Authentication service unavailable' });
}
// Delegate to SDK's login_client method
await client.login_client(req, res);
});
module.exports = router;Logout Implementation
// Logout invalidates the JWT token and closes the session
router.post('/logout', authenticate, async (req, res) => {
req.logAction = 'logout-attempt';
const client = req.app.locals.roditClient;
if (!client) {
return res.status(503).json({ error: 'Authentication service unavailable' });
}
// Delegate to SDK's logout_client method
await client.logout_client(req, res);
});Protected Routes
// Require authentication for access
app.get('/api/data', authenticate, (req, res) => {
// req.user contains authenticated user information
const logger = req.app.locals.roditClient.getLogger();
logger.info('Protected route accessed', {
component: 'API',
userId: req.user.id,
roditId: req.user.roditId,
requestId: req.requestId
});
res.json({
message: 'Authenticated data',
user: req.user,
requestId: req.requestId
});
});Authentication Middleware
The authenticate middleware validates JWT tokens and populates req.user:
const authenticate = (req, res, next) => {
const client = req.app.locals.roditClient;
return client.authenticate(req, res, next);
};
// After successful authentication, req.user contains:
// {
// id: 'user-unique-id',
// roditId: '01K4G3D95QF6NR0RSJK9WEK6KA',
// aud: 'audience',
// iss: 'issuer',
// exp: 1640999999,
// iat: 1640995200,
// session_id: '01HQXYZ123ABC'
// }Login Mode Control
The SDK provides configurable access control for RODiT authentication, allowing you to restrict which types of logins are accepted by your server.
Login Types
Partner Login (Client-Server)
- Definition: Authentication where the peer's service provider ID is different from the server's service provider ID
- Use Case: Traditional client-server authentication where a client authenticates to a service provider
- Example: A mobile app (client) authenticating to your API server
Peer Login (Peer-to-Peer)
- Definition: Authentication where the peer's service provider ID is the same as the server's service provider ID
- Use Case: Peer-to-peer authentication between entities with the same service provider
- Example: Two servers in the same organization authenticating to each other
Configuration Options
| Mode | Partner Logins | Peer Logins | Description |
|------|---------------|-------------|-------------|
| partner | ✅ Accepted | ❌ Rejected | Default - Only accept client-server authentication |
| promiscuous | ✅ Accepted | ✅ Accepted | Accept all valid logins regardless of type |
| p2p | ❌ Rejected | ✅ Accepted | Only accept peer-to-peer authentication |
Usage Examples
Default (Partner Only):
# No configuration needed - this is the default
# Only client-server authentication is acceptedAccept All Logins:
export SECURITY_OPTIONS_LOGIN_MODE=promiscuous
# Both Partner and Peer logins are acceptedPeer-to-Peer Only:
export SECURITY_OPTIONS_LOGIN_MODE=p2p
# Only peer-to-peer authentication is acceptedDocker/Podman:
podman run -e SECURITY_OPTIONS_LOGIN_MODE=partner ...GitHub Actions: Add repository variable:
- Name:
SECURITY_OPTIONS_LOGIN_MODE - Value:
partner|promiscuous|p2p
Logging and Monitoring
Successful Login:
{
"level": "info",
"message": "PARTNER login verified successfully",
"verificationType": "PARTNER",
"loginMode": "partner",
"duration": 1234
}Rejected Login:
{
"level": "warn",
"message": "PEER login rejected by LOGIN_MODE policy",
"verificationType": "PEER",
"loginMode": "partner",
"policyReason": "LOGIN_MODE=partner does not accept PEER logins"
}Metrics:
rodit_match_verificationwithresult: "success"- Successful authenticationrodit_match_verificationwithresult: "policy_rejected"- Rejected by policy
Security Considerations
- Default is Secure: The default
partnermode provides the most restrictive access control - Promiscuous Mode: Use only when you need to accept both types of authentication
- P2P Mode: Use when building peer-to-peer systems where only same-provider authentication is needed
- Policy Enforcement: Rejections are logged with clear reasons for audit trails
Troubleshooting
Login Rejected with "policy_rejected":
- If you see "PEER login rejected" and need to accept peer logins, set mode to
promiscuousorp2p - If you see "PARTNER login rejected" and need to accept partner logins, set mode to
promiscuousorpartner
Check Current Mode: Look for the log message during authentication:
"Starting RODiT match verification" with "loginMode": "partner"Authorization & Permissions
Route-Based Permissions
Permissions are configured in your RODiT token metadata using the permissioned_routes field:
{
"permissioned_routes": {
"entities": {
"/": {
"methods": "+0"
},
"/api/echo": {
"methods": "+0"
},
"/api/cruda/create": {
"methods": "+0"
},
"/api/cruda/list": {
"methods": "+0"
},
"/api/admin": {
"methods": "+0"
}
}
}
}Permission Format:
"+0"= All methods allowed (GET, POST, PUT, DELETE, etc.)"+1"= GET only"+2"= POST only- Custom combinations can be defined
Permission Validation Middleware
The authorize middleware validates that the authenticated user has permission to access the requested route:
const authenticate = (req, res, next) => {
return req.app.locals.roditClient.authenticate(req, res, next);
};
const authorize = (req, res, next) => {
return req.app.locals.roditClient.authorize(req, res, next);
};
// Apply both authentication and authorization
app.use('/api/admin', authenticate, authorize, adminRoutes);
// CRUDA endpoints with full protection
app.use('/api/cruda', authenticate, authorize, crudaRoutes);Permission Enforcement
// Example: CRUDA routes with permission checking
const router = express.Router();
// All routes require authentication + authorization
router.post('/create', async (req, res) => {
// User must have permission for POST /api/cruda/create
const { comment, author } = req.body;
// Create record in database
const result = await db.run(
'INSERT INTO comments (comment, author) VALUES (?, ?)',
[comment, author || req.user.roditId]
);
res.json({ id: result.lastID, requestId: req.requestId });
});
router.post('/list', async (req, res) => {
// User must have permission for POST /api/cruda/list
const records = await db.all('SELECT * FROM comments ORDER BY created_at DESC');
res.json({ records, requestId: req.requestId });
});
module.exports = router;Dynamic Permission Checking
// Check permissions programmatically
const client = req.app.locals.roditClient;
const hasPermission = client.isOperationPermitted('POST', '/api/admin/users');
if (!hasPermission) {
return res.status(403).json({
error: 'Forbidden',
message: 'You do not have permission to access this resource',
requestId: req.requestId
});
}
// Proceed with operationPermission Validation in Client Token Minting
When minting client tokens via /api/signclient, the server validates that requested permissions are a subset of the server's own permissions:
// Client requests these permissions:
const requestedPermissions = {
"/": "+0",
"/api/echo": "+0",
"/api/cruda/create": "+0"
};
// Server validates against its own permissioned_routes
// If any requested route is not in server's config, request is rejected with HTTP 400Session Management
Overview
The SDK includes a comprehensive session management system that:
- Tracks active user sessions
- Validates JWT tokens against session state
- Supports pluggable storage backends
- Automatically cleans up expired sessions
- Integrates with performance metrics
Session Storage Backends
1. In-Memory Storage (Default)
No configuration needed - works out of the box:
const client = await RoditClient.create('server');
// Uses InMemorySessionStorage by defaultPros: Fast, zero configuration
Cons: Sessions lost on server restart, not suitable for multi-server deployments
2. SQLite Storage (Recommended for Production)
Persistent storage using SQLite database:
const express = require('express');
const session = require('express-session');
const SQLiteStore = require('connect-sqlite3')(session);
const { RoditClient, setExpressSessionStore } = require('@rodit/rodit-auth-be');
// Configure BEFORE initializing RoditClient
const sessionStore = new SQLiteStore({
db: 'sessions.db',
dir: './data',
table: 'sessions'
});
setExpressSessionStore(sessionStore);
// Now initialize client
const client = await RoditClient.create('server');Pros: Persistent across restarts, simple setup, uses existing database infrastructure
Cons: Not suitable for multi-server deployments
3. Redis Storage (For Multi-Server)
npm install express-session connect-redis redisconst session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const { setExpressSessionStore } = require('@rodit/rodit-auth-be');
// Create Redis client
const redisClient = createClient({
url: process.env.REDIS_URL || 'redis://127.0.0.1:6379'
});
await redisClient.connect();
// Create Redis store
const redisStore = new RedisStore({
client: redisClient,
prefix: 'rodit:sess:',
ttl: 86400 // 24 hours
});
setExpressSessionStore(redisStore);
const client = await RoditClient.create('server');Pros: Shared sessions across multiple servers, high performance
Cons: Requires Redis infrastructure
Session Storage Configuration
The SDK supports configurable session storage via the SESSION_STORAGE_TYPE environment variable.
Storage Type Options
1. "memory" (Default)
- Uses SDK's standalone
InMemorySessionStorage - No external dependencies required
- Sessions stored in JavaScript
Map - Sessions lost on server restart
- Suitable for development or single-instance deployments
export SESSION_STORAGE_TYPE=memory2. "express" or "express-session"
- Uses
express-sessioncompatible stores - Requires
express-sessionto be installed - Defaults to
express-sessionMemoryStore - Can be overridden with
setExpressSessionStore()for Redis, SQLite, etc. - Suitable for production with persistent storage
export SESSION_STORAGE_TYPE=express-sessionConfiguring Persistent Storage
SQLite Example:
const session = require('express-session');
const SQLiteStore = require('connect-sqlite3')(session);
const { setExpressSessionStore } = require('@rodit/rodit-auth-be');
// Configure BEFORE initializing RoditClient
const sessionStore = new SQLiteStore({
db: 'sessions.db',
dir: './data',
table: 'sessions'
});
setExpressSessionStore(sessionStore);
// Now initialize client
const client = await RoditClient.create('server');Redis Example:
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const { setExpressSessionStore } = require('@rodit/rodit-auth-be');
const redisClient = createClient({
url: process.env.REDIS_URL || 'redis://127.0.0.1:6379'
});
await redisClient.connect();
const redisStore = new RedisStore({
client: redisClient,
prefix: 'rodit:sess:',
ttl: 86400
});
setExpressSessionStore(redisStore);Session Configuration Variables
# Storage backend type
export SESSION_STORAGE_TYPE=express-session
# Cleanup interval (milliseconds) - how often to remove expired sessions
export SESSION_CLEANUP_INTERVAL=3600000 # 1 hour
# Token retention period (seconds) - how long to keep closed sessions
export SESSION_TOKEN_RETENTION_PERIOD=604800 # 7 days
# Validation cache TTL (milliseconds) - trades security for performance
# Lower = more secure but more storage lookups
# Higher = faster but longer window after logout where token may still work
# Set to 0 to disable caching (always check session state)
export SESSION_VALIDATION_CACHE_TTL=5000 # 5 secondsSession Validation Cache:
The SDK caches token validation results to reduce storage lookups:
- Enabled by default with 5-second TTL
- Trade-off: Performance vs. security
- After logout: Cache is immediately invalidated for that session
- Recommendation: Keep default (5s) for most use cases
- High security: Set to
0to disable caching
// Get cache statistics
const sessionManager = roditClient.getSessionManager();
const cacheStats = sessionManager.getValidationCacheStats();
console.log('Cache stats:', cacheStats);
// Output: { totalEntries: 10, validEntries: 8, expiredEntries: 2, cacheTTL: 5000, cacheEnabled: true }Session Operations
// Get session manager
const sessionManager = roditClient.getSessionManager();
// Get active session count
const activeCount = await sessionManager.getActiveSessionCount();
// Get storage information
const storageInfo = await sessionManager.getStorageInfo();
console.log('Storage type:', storageInfo.type);
console.log('Session count:', storageInfo.sessionCount);
// Enumerate sessions via storage
const allSessions = await sessionManager.storage.getAll();
// Or fallback using keys() + get()
const sessionIds = await sessionManager.storage.keys();
const sessions = [];
for (const id of sessionIds) {
const session = await sessionManager.storage.get(id);
if (session) sessions.push(session);
}
// Check if token is invalidated
const isInvalidated = await sessionManager.isTokenInvalidated(jwtToken);
// Get detailed invalidation info
const invalidationInfo = await sessionManager.getTokenInvalidationInfo(jwtToken);
if (invalidationInfo) {
console.log('Invalidation reason:', invalidationInfo.reason);
console.log('Invalidated at:', invalidationInfo.invalidatedAt);
}
// Manually close a session
await sessionManager.closeSession(sessionId, 'admin_action');
// Run manual cleanup (removes expired sessions)
const cleanup = await sessionManager.runManualCleanup();
console.log(`Removed ${cleanup.removedSessionsCount} expired sessions`);
// Get validation cache statistics
const cacheStats = sessionManager.getValidationCacheStats();
console.log('Cache entries:', cacheStats.totalEntries);
console.log('Cache TTL:', cacheStats.cacheTTL);Session Lifecycle
- Login - Session created, JWT token issued with session ID
- Active - Token validated on each request, session last_accessed updated
- Logout - Session closed, token invalidated, termination token issued
- Expiration - Sessions automatically expire based on JWT duration
- Cleanup - Expired sessions removed by automatic cleanup process
Token Invalidation
The SDK validates tokens by checking session state:
// Authentication middleware checks:
// 1. JWT signature validity
// 2. JWT expiration
// 3. Session exists and is active
// 4. Session not expired
// After logout, tokens are invalidated because:
// - Session status set to 'closed'
// - Subsequent requests fail authenticationConfiguration
Configuration Priority
The SDK automatically configures itself from multiple sources with a clear priority hierarchy:
- Environment Variables (Highest priority) - Direct
process.envaccess - Host Application Config - Values from
configpackage (with env mappings) - SDK Fallback Defaults - Built-in defaults from
configsdk.js - Provided Default Value - Optional parameter to
config.get()
Example:
const config = roditClient.getConfig();
// Priority 1: Checks process.env.SESSION_STORAGE_TYPE
// Priority 2: Checks host config.get('SESSION_STORAGE_TYPE')
// Priority 3: Uses SDK default 'memory'
// Priority 4: Falls back to 'memory' if provided
const storageType = config.get('SESSION_STORAGE_TYPE', 'memory');This ensures that:
- CI/CD environment variables always take precedence
- Host applications can override SDK defaults
- SDK provides sensible defaults for all settings
- Configuration is predictable and debuggable
Automatic Configuration Loading
The SDK loads configuration from multiple sources:
- Environment Variables - Direct environment access
- Configuration Files - config/default.json, config/production.json
- Vault Credentials - Production credential storage
- SDK Defaults - Fallback values
Environment Configuration: NODE_ENV and LOG_LEVEL
The SDK uses two separate environment variables for configuration, following Node.js ecosystem standards:
NODE_ENV - Environment Type & Security Behavior
Controls environment-specific behavior and security settings:
Values:
production- Production environment (strict security, no error details)development- Development environment (relaxed security, detailed errors)test- Testing environment (allows bypasses for automated testing)staging- Staging environment (production security with optional verbose logging)
Default: production (secure by default)
Controls:
- ✅ Error detail exposure in API responses
- ✅ Peer public key requirement enforcement
- ✅ Webhook verification bypass (test mode only)
- ✅ Security-critical behavior
LOG_LEVEL - Logging Verbosity
Controls Winston logger verbosity independently from environment:
Values:
error- Only errorswarn- Warnings and errorsinfo- Informational messages, warnings, and errors (recommended for production)debug- Detailed debugging informationtrace- Maximum verbosity with full traces
Default: info
Controls:
- ✅ Winston logger output level
- ✅ Debug payload logging
- ✅ Log verbosity only (not security)
Separation of Concerns
// Environment detection (security)
const isProduction = process.env.NODE_ENV === 'production';
const isDevelopment = process.env.NODE_ENV === 'development';
const isTest = process.env.NODE_ENV === 'test';
// Logging verbosity (independent)
const config = roditClient.getConfig();
const logLevel = config.get('LOG_LEVEL', 'info');Configuration Examples
Production (normal):
export NODE_ENV=production
export LOG_LEVEL=info
# Results in:
# - Strict security enforcement
# - No error details in responses
# - Minimal logging outputProduction (troubleshooting):
export NODE_ENV=production
export LOG_LEVEL=debug
# Results in:
# - Strict security enforcement (still production)
# - No error details in responses (still secure)
# - Verbose logging for debuggingDevelopment:
export NODE_ENV=development
export LOG_LEVEL=debug
# Results in:
# - Relaxed security for development
# - Detailed error messages in responses
# - Verbose loggingTesting:
export NODE_ENV=test
export LOG_LEVEL=error
# Results in:
# - Test mode (allows bypasses)
# - Detailed error messages
# - Only errors logged (cleaner test output)Staging:
export NODE_ENV=production
export LOG_LEVEL=warn
# Results in:
# - Production security
# - No error details exposed
# - Only warnings and errors loggedBehavior Matrix
| Scenario | NODE_ENV | LOG_LEVEL | Security | Error Details | Logging |
|----------|----------|-----------|----------|---------------|---------|
| Production | production | info | ✅ Strict | ❌ Hidden | Minimal |
| Production Debug | production | debug | ✅ Strict | ❌ Hidden | Verbose |
| Development | development | debug | ⚠️ Relaxed | ✅ Shown | Verbose |
| Testing | test | error | ⚠️ Bypass OK | ✅ Shown | Errors only |
| Staging | production | warn | ✅ Strict | ❌ Hidden | Warnings |
Vault-Based Configuration (Production)
For production deployments, credentials are loaded from HashiCorp Vault:
# Environment variables for vault
export RODIT_NEAR_CREDENTIALS_SOURCE=vault
export VAULT_ENDPOINT=https://vault.example.com
export VAULT_ROLE_ID=your-role-id
export VAULT_SECRET_ID=your-secret-id
export VAULT_RODIT_KEYVALUE_PATH=secret/rodit
export SERVICE_NAME=your-service-name
export NEAR_CONTRACT_ID=your-contract.testnetFile-Based Configuration (Development)
For development, credentials can be loaded from files:
export RODIT_NEAR_CREDENTIALS_SOURCE=file
export CREDENTIALS_FILE_PATH=./credentials/rodit-credentials.jsonAccessing Configuration
// Get complete RODiT configuration
const configObject = await roditClient.getConfigOwnRodit();
const metadata = configObject.own_rodit.metadata;
// Access RODiT token metadata
const jwtDuration = metadata.jwt_duration; // JWT expiration time
const maxRequests = metadata.max_requests; // Rate limit
const maxRqWindow = metadata.maxrq_window; // Rate limit window
const apiEndpoint = metadata.subjectuniqueidentifier_url; // API URL
const webhookUrl = metadata.webhook_url; // Webhook endpoint
// Parse permissioned routes
const permissionedRoutes = JSON.parse(metadata.permissioned_routes || '{}');
// Use SDK config for application settings
const config = roditClient.getConfig();
const logLevel = config.get('LOG_LEVEL', 'info');
const dbPath = config.get('API_DEFAULT_OPTIONS.DB_PATH');Dynamic Rate Limiting
// Configure rate limiting from RODiT token
const configObject = await roditClient.getConfigOwnRodit();
const metadata = configObject.own_rodit.metadata;
if (metadata.max_requests && metadata.maxrq_window) {
const maxRequests = parseInt(metadata.max_requests);
const windowSeconds = parseInt(metadata.maxrq_window);
const rateLimiter = roditClient.getRateLimitMiddleware();
app.use(rateLimiter(maxRequests, windowSeconds));
}Environment Variables
Complete list of SDK environment variables:
Core Configuration
# Service identification
export SERVICE_NAME=your-service-name
export API_VERSION=1.0.0
# Environment and logging
export NODE_ENV=production # production, development, test, staging
export LOG_LEVEL=info # error, warn, info, debug, traceCredentials and Authentication
# Credential source
export RODIT_NEAR_CREDENTIALS_SOURCE=vault # vault, file, env
# Vault configuration (production)
export VAULT_ENDPOINT=https://vault.example.com
export VAULT_ROLE_ID=your-role-id
export VAULT_SECRET_ID=your-secret-id
export VAULT_RODIT_KEYVALUE_PATH=secret/rodit
export VAULT_TOKEN_TTL=3600
# File-based credentials (development)
export CREDENTIALS_FILEPATH=./credentials/rodit.json
# NEAR blockchain
export NEAR_CONTRACT_ID=your-contract.testnet
export NEAR_RPC_URL=https://rpc.testnet.fastnear.com
export NEAR_RPC_CACHE_TTL=5000 # millisecondsSession Management
# Session storage configuration
export SESSION_STORAGE_TYPE=express-session # memory, express, express-session
export SESSION_CLEANUP_INTERVAL=3600000 # milliseconds (1 hour)
export SESSION_TOKEN_RETENTION_PERIOD=604800 # seconds (7 days)
export SESSION_VALIDATION_CACHE_TTL=5000 # milliseconds (5 seconds)Logging and Monitoring
# Loki logging
export LOKI_URL=https://loki.example.com:3100
export LOKI_BASIC_AUTH=username:password
export LOKI_TLS_SKIP_VERIFY=false # true to skip TLS verificationSecurity Options
# Webhook configuration
export WEBHOOK_TLS_SKIP_VERIFY=false # true to skip TLS verification
# Login mode control (see Login Mode section below)
export SECURITY_OPTIONS_LOGIN_MODE=partner # partner, promiscuous, or p2p
# Security thresholds
export SECURITY_OPTIONS_LAPSED_LIFETIME_PROPORTION_4RENEWAL_ELIGIBILITY=0.80
export SECURITY_OPTIONS_THRESHOLD_VALIDATION_TYPE=0.10
export SECURITY_OPTIONS_DURATIONRAMP=0.85
export SECURITY_OPTIONS_SERVERORCLIENT=SERVER-INITIATED
export SECURITY_OPTIONS_SILENT_LOGIN_FAILURES=falseDatabase Configuration
export API_DEFAULT_OPTIONS_DB_PATH=/app/data/database.sqliteLogging & Monitoring
Structured Logging
The SDK provides comprehensive structured logging:
const { logger } = require('@rodit/rodit-auth-be');
// Basic logging
logger.info('Operation completed', {
component: 'UserService',
operation: 'createUser',
userId: '123',
duration: 150
});
// Context-aware logging
logger.infoWithContext('Request processed', {
component: 'API',
method: 'POST',
path: '/api/users',
requestId: req.requestId,
userId: req.user?.id,
duration: Date.now() - req.startTime
});
// Error logging with metrics
logger.errorWithContext('Operation failed', {
component: 'UserService',
operation: 'createUser',
requestId: req.requestId,
error: error.message,
stack: error.stack
}, error);Loki with the SDK (canonical)
Use this as the authoritative guide for configuring logging with the SDK.
Environment variables
export LOKI_URL=https://<your-loki-host>:3100
export LOKI_BASIC_AUTH="username:password" # store in secrets
export LOKI_TLS_SKIP_VERIFY=true # only for self-signed/test
export LOG_LEVEL=info
export SERVICE_NAME=clienttestapi-apiThese are already mapped in config/custom-environment-variables.json, so container/CI env vars will flow into the app.
How the SDK selects/configures the logger
- Default: JSON to stdout only (no Loki). Honors
LOG_LEVEL, addsservice_name. - Production: Create a Winston logger with a
winston-lokitransport and inject it once:logger.setLogger(customLogger). - Access:
const { logger } = require('@rodit/rodit-auth-be')orroditClient.getLogger()both delegate to the same facade.
Direct-to-Loki via winston-loki (recommended)
const { logger } = require('@rodit/rodit-auth-be');
const winston = require('winston');
const LokiTransport = require('winston-loki');
const transports = [new winston.transports.Console({ format: winston.format.json() })];
if (process.env.LOKI_URL) {
const lokiOptions = {
host: process.env.LOKI_URL,
basicAuth: process.env.LOKI_BASIC_AUTH, // Basic Auth for Loki
labels: { app: process.env.SERVICE_NAME || 'clienttestapi-api', component: 'rodit-sdk' },
json: true,
batching: true
};
if ((process.env.LOKI_TLS_SKIP_VERIFY || '').toLowerCase() === 'true') {
lokiOptions.ssl = { rejectUnauthorized: false };
}
transports.push(new LokiTransport(lokiOptions));
}
const customLogger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
transports
});
logger.setLogger(customLogger);CI/CD notes
.github/workflows/deploy.ymlpassesLOKI_URL,LOKI_TLS_SKIP_VERIFY,LOKI_BASIC_AUTHinto the container;src/app.jsconfig injects the transport at startup.- Store
LOKI_BASIC_AUTHin CI/CD secrets; never commit credentials.
Quick verification
- Start the app with
LOKI_URLandLOKI_BASIC_AUTHset. - Emit a test log:
logger.info('Loki test', { component: 'SmokeTest' }). - In Grafana Explore, query with
{app="clienttestapi-api"}and confirm logs.
Performance Tracking
The SDK includes comprehensive performance tracking and metrics collection.
Performance Service
const performanceService = roditClient.getPerformanceService();
// Record incoming request
performanceService.recordRequest(req);
// Record custom metrics with labels
performanceService.recordMetric('operation_duration', 150, {
operation: 'db_query',
table: 'users',
status: 'success'
});
// Record errors
performanceService.recordMetric('error_count', 1, {
method: req.method,
path: req.path,
status: res.statusCode
});
// Get aggregated metrics
const metrics = performanceService.getMetrics();
console.log('Total requests:', metrics.totalRequests);
console.log('Error count:', metrics.errorCount);
console.log('Average response time:', metrics.avgResponseTime);Automatic Request Tracking
Integrate performance tracking into your middleware:
// Performance monitoring middleware
app.use((req, res, next) => {
req.startTime = Date.now();
const performanceService = roditClient.getPerformanceService();
if (performanceService) {
performanceService.recordRequest(req);
}
res.on('finish', () => {
const duration = Date.now() - req.startTime;
if (performanceService) {
// Record request duration
performanceService.recordMetric('request_duration_ms', duration, {
method: req.method,
path: req.path,
status: res.statusCode
});
// Record errors
if (res.statusCode >= 400) {
performanceService.recordMetric('error_count', 1, {
method: req.method,
path: req.path,
status: res.statusCode
});
}
}
});
next();
});Session Performance Metrics
Track session-related performance:
const sessionManager = roditClient.getSessionManager();
// Get validation cache statistics
const cacheStats = sessionManager.getValidationCacheStats();
logger.info('Session cache performance', {
component: 'SessionManager',
totalEntries: cacheStats.totalEntries,
validEntries: cacheStats.validEntries,
expiredEntries: cacheStats.expiredEntries,
cacheTTL: cacheStats.cacheTTL,
cacheEnabled: cacheStats.cacheEnabled
});
// Get storage information
const storageInfo = await sessionManager.getStorageInfo();
logger.info('Session storage status', {
component: 'SessionManager',
storageType: storageInfo.type,
sessionCount: storageInfo.sessionCount,
timestamp: storageInfo.timestamp
});Custom Metrics
Record application-specific metrics:
const performanceService = roditClient.getPerformanceService();
// Database operation timing
const dbStart = Date.now();
const result = await db.query('SELECT * FROM users');
const dbDuration = Date.now() - dbStart;
performanceService.recordMetric('db_query_duration', dbDuration, {
operation: 'select',
table: 'users',
rowCount: result.length
});
// External API call timing
const apiStart = Date.now();
const apiResponse = await fetch('https://api.example.com/data');
const apiDuration = Date.now() - apiStart;
performanceService.recordMetric('external_api_duration', apiDuration, {
endpoint: 'api.example.com',
status: apiResponse.status,
success: apiResponse.ok
});
// Business metrics
performanceService.recordMetric('user_action', 1, {
action: 'comment_created',
userId: req.user.id,
timestamp: new Date().toISOString()
});Webhooks
Overview
The SDK supports sending webhooks for important events. Webhook URLs are configured in the RODiT token metadata. Webhooks are configured in your RODiT token:
{
"webhook_url": "https://webhook.example.com:3444",
"webhook_cidr": "0.0.0.0/0"
}Sending Webhooks
// Get webhook handler from client
const roditClient = req.app.locals.roditClient;
// Send webhook for an event
const webhookPayload = {
event: 'comment_created',
data: {
id: comment.id,
author: comment.author,
timestamp: new Date().toISOString()
},
isError: false
};
try {
const result = await roditClient.send_webhook(webhookPayload, req);
if (result.success) {
logger.info('Webhook sent successfully', {
component: 'CRUDA',
event: webhookPayload.event,
requestId: req.requestId
});
}
} catch (error) {
// Webhook failures don't crash the application
logger.warn('Webhook delivery failed', {
component: 'CRUDA',
event: webhookPayload.event,
error: error.message,
requestId: req.requestId
});
}Webhook Error Handling
// Graceful webhook handling in CRUDA operations
const logAndSendWebhook = async (payload, req = null) => {
try {
const roditClient = req?.app?.locals?.roditClient;
if (!roditClient) {
logger.warn('RoditClient not available, skipping webhook', {
component: 'CRUDA',
event: payload?.event
});
return { success: false, error: 'RoditClient not available' };
}
return await roditClient.send_webhook(payload, req);
} catch (error) {
// Log but don't throw - webhook failures shouldn't crash the app
logger.error('Webhook delivery failed', {
component: 'CRUDA',
event: payload?.event,
error: error.message
});
return { success: false, error: error.message };
}
};Advanced Usage
Route Module Pattern
Create reusable route modules that access the shared RoditClient:
// routes/protected.js
const express = require('express');
const { logger } = require('@rodit/rodit-auth-be');
const router = express.Router();
// Middleware that uses the shared client
const authenticate = (req, res, next) => {
const client = req.app.locals.roditClient;
if (!client) {
return res.status(503).json({ error: 'Authentication service unavailable' });
}
return client.authenticate(req, res, next);
};
const authorize = (req, res, next) => {
const client = req.app.locals.roditClient;
if (!client) {
return res.status(503).json({ error: 'Authentication service unavailable' });
}
return client.authorize(req, res, next);
};
// Protected route with full authentication and authorization
router.get('/data', authenticate, authorize, async (req, res) => {
const startTime = Date.now();
try {
// Your business logic here
const data = await processUserData(req.user.id);
logger.infoWithContext('Data retrieved successfully', {
component: 'ProtectedRoutes',
method: 'getData',
userId: req.user.id,
requestId: req.requestId,
duration: Date.now() - startTime
});
res.json({ data, requestId: req.requestId });
} catch (error) {
logger.errorWithContext('Failed to retrieve data', {
component: 'ProtectedRoutes',
method: 'getData',
userId: req.user.id,
requestId: req.requestId,
duration: Date.now() - startTime,
error: error.message
}, error);
res.status(500).json({
error: 'Internal server error',
requestId: req.requestId
});
}
});
module.exports = router;Portal Authentication (Server-to-Server)
For server-to-server authentication (e.g., minting client tokens):
// routes/signclient.js
const router = express.Router();
router.post('/signclient', authenticate, authorize, async (req, res) => {
const { tobesignedValues, mintingfee, mintingfeeaccount } = req.body;
const client = req.app.locals.roditClient;
const logger = client.getLogger();
try {
// Validate requested permissions against server's permissions
const configObject = await client.getConfigOwnRodit();
const serverPermissions = JSON.parse(
configObject.own_rodit.metadata.permissioned_routes || '{}'
);
const requestedPermissions = JSON.parse(
tobesignedValues.permissioned_routes || '{}'
);
// Validate that all requested routes exist in server config
// (Implementation details in actual code)
// Authenticate to portal and mint client token
const port = configObject.port || 8443;
const result = await client.login_portal(configObject, port);
if (result.error) {
return res.status(500).json({
error: 'Portal authentication failed',
details: result.message,
requestId: req.requestId
});
}
// Sign the client token via portal
const signedToken = await signPortalRodit(
port,
tobesignedValues,
mintingfee,
mintingfeeaccount,
client
);
res.json({
signedToken,
requestId: req.requestId
});
} catch (error) {
logger.errorWithContext('Client token minting failed', {
component: 'SignClient',
requestId: req.requestId,
error: error.message
}, error);
res.status(500).json({
error: 'Token minting failed',
requestId: req.requestId
});
}
});
module.exports = router;SignPortal URL Configuration
Overview
When performing server-to-server authentication with SignPortal (e.g., minting client tokens), the SDK automatically constructs the SignPortal URL from the serviceprovider_id field in your RODiT token metadata.
Smart Contract Name Format
The SignPortal URL is derived from the smart contract component (sc=) in your serviceprovider_id. The SDK supports two formats:
Standard Format (3+ components):
sc=<number>-<domain>-<tld>.nearExample:
serviceprovider_id: "bc=near.org;sc=10975-discernible-org.near;id=..."Parsing:
- Split by
.:["10975-discernible-org", "near"] - Take first part:
10975-discernible-org - Split by
-:["10975", "discernible", "org"] - Extract domain:
discernible(index 1) - Extract TLD:
org(index 2) - Result:
https://signportal.discernible.org:8443
Alternative Format (2 components):
sc=<domain>-<tld>.nearExample:
serviceprovider_id: "bc=near.org;sc=roditcorp-com.near;id=..."Parsing:
- Split by
.:["roditcorp-com", "near"] - Take first part:
roditcorp-com - Split by
-:["roditcorp", "com"] - Extract domain:
roditcorp(index 0) - Extract TLD:
com(index 1) - Result:
https://signportal.roditcorp.com:8443
serviceprovider_id Structure
The complete serviceprovider_id format:
bc=<blockchain>;sc=<smart-contract>;id=<identifier>[;id=<additional-id>]Components:
bc=- Blockchain identifier (e.g.,near.org)sc=- Smart contract name (used to construct SignPortal URL)id=- One or more identifier components
Example:
{
"serviceprovider_id": "bc=near.org;sc=roditcorp-com.near;id=01K8QECHMKFVNWQ54PJ2W2GMA7;id=01K8QECHMM1214VMDHSH7JM6H8"
}URL Construction Method
The SDK uses roditClient.getPortalUrl(serviceProviderId, port) to construct the SignPortal URL:
const client = req.app.locals.roditClient;
const configObject = await client.getConfigOwnRodit();
const serviceProviderId = configObject.own_rodit.metadata.serviceprovider_id;
const portalPort = 8443;
// Automatically constructs: https://signportal.<domain>.<tld>:8443
const portalUrl = client.getPortalUrl(serviceProviderId, portalPort);Troubleshooting
Error: "Failed to parse URL from " (empty string)
- Cause:
serviceprovider_idis empty or undefined in your RODiT configuration - Solution: Verify your RODiT token has a valid
serviceprovider_idfield - Check: Run
./infra/roditwallet.sh <private-key> <token-id>to view token metadata
Error: "Invalid serviceprovider_id format: missing sc= component"
- Cause: The
serviceprovider_iddoesn't contain ansc=component - Solution: Ensure your token includes the smart contract identifier
- Format:
bc=near.org;sc=<contract-name>.near;id=...
Error: "Invalid domain format in smart contract"
- Cause: Smart contract name has fewer than 2 components when split by
- - Solution: Use format
<domain>-<tld>or<number>-<domain>-<tld> - Valid:
roditcorp-com.near,10975-discernible-org.near - Invalid:
roditcorp.near,mycontract.near
Configuration Verification
To verify your SignPortal URL configuration:
const client = req.app.locals.roditClient;
const logger = client.getLogger();
try {
const configObject = await client.getConfigOwnRodit();
const serviceProviderId = configObject.own_rodit.metadata.serviceprovider_id;
logger.info('RODiT Configuration', {
component: 'SignPortal',
serviceProviderId,
hasServiceProviderId: !!serviceProviderId
});
if (serviceProviderId) {
const portalUrl = client.getPortalUrl(serviceProviderId, 8443);
logger.info('SignPortal URL constructed', {
component: 'SignPortal',
portalUrl
});
}
} catch (error) {
logger.error('SignPortal URL construction failed', {
component: 'SignPortal',
error: error.message
});
}CRUDA Operations Example
Complete CRUD implementation with authentication, authorization, webhooks, and performance tracking:
// protected/cruda.js
const express = require('express');
const router = express.Router();
const { RoditClient } = require('@rodit/rodit-auth-be');
const sqlite3 = require('sqlite3');
const { open } = require('sqlite');
const { ulid } = require('ulid');
const sdkClient = new RoditClient();
const logger = sdkClient.getLogger();
let db;
// Initialize database
const initializeDatabase = async () => {
db = await open({
filename: '/app/data/database.sqlite',
driver: sqlite3.Database
});
await db.run(`CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
comment TEXT NOT NULL,
author TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
};
// Webhook helper
const logAndSendWebhook = async (payload, req) => {
try {
const roditClient = req?.app?.locals?.roditClient;
if (!roditClient) return { success: false };
return await roditClient.send_webhook(payload, req);
} catch (error) {
logger.error('Webhook failed', { error: error.message });
return { success: false, error: error.message };
}
};
// CREATE
router.post('/create', async (req, res) => {
const { comment, author } = req.body;
const requestId = req.requestId || ulid();
try {
const result = await db.run(
'INSERT INTO comments (comment, author) VALUES (?, ?)',
[comment, author || req.user.roditId]
);
// Send webhook
await logAndSendWebhook({
event: 'comment_created',
data: { id: result.lastID, comment, author },
isError: false
}, req);
res.json({ id: result.lastID, requestId });
} catch (error) {
logger.errorWithContext('Create failed', {
component: 'CRUDA',
error: error.message,
requestId
}, error);
res.status(500).json({ error: 'Create failed', requestId });
}
});
// LIST
router.post('/list', async (req, res) => {
try {
const records = await db.all(
'SELECT * FROM comments ORDER BY created_at DESC'
);
res.json({ records, requestId: req.requestId });
} catch (error) {
res.status(500).json({ error: 'List failed', requestId: req.requestId });
}
});
// READ
router.post('/read', async (req, res) => {
const { id } = req.body;
try {
const record = await db.get('SELECT * FROM comments WHERE id = ?', [id]);
if (!record) {
return res.status(404).json({ error: 'Not found', requestId: req.requestId });
}
res.json({ record, requestId: req.requestId });
} catch (error) {
res.status(500).json({ error: 'Read failed', requestId: req.requestId });
}
});
// UPDATE
router.post('/update', async (req, res) => {
const { id, comment } = req.body;
try {
await db.run(
'UPDATE comments SET comment = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
[comment, id]
);
await logAndSendWebhook({
event: 'comment_updated',
data: { id, comment },
isError: false
}, req);
res.json({ success: true, requestId: req.requestId });
} catch (error) {
res.status(500).json({ error: 'Update failed', requestId: req.requestId });
}
});
// DELETE
router.post('/destroy', async (req, res) => {
const { id } = req.body;
try {
await db.run('DELETE FROM comments WHERE id = ?', [id]);
await logAndSendWebhook({
event: 'comment_deleted',
data: { id },
isError: false
}, req);
res.json({ success: true, requestId: req.requestId });
} catch (error) {
res.status(500).json({ error: 'Delete failed', requestId: req.requestId });
}
});
// Export initialization function
module.exports = router;
module.exports.initializeDatabase = initializeDatabase;API Reference
RoditClient Class
The main client class for all RODiT operations.
Static Methods
RoditClient.create(role)
Create and initialize a RODiT client in one step.
const client = await RoditClient.create('server'); // For server applications
const client = await RoditClient.create('client'); // For client applications
const client = await RoditClient.create('portal'); // For portal authenticationParameters:
role(string): Client role -'server','client', or'portal'
Returns: Promise<RoditClient> - Fully initialized client instance
Throws: Error if initialization fails (e.g., missing credentials, Vault connection failure)
Instance Methods
authenticate(req, res, next)
Express middleware for authenticating API requests. Validates JWT tokens and populates req.user.
const authenticate = (req, res, next) => roditClient.authenticate(req, res, next);
app.use('/api/protected', authenticate, handler);Validates:
- JWT signature
- JWT expiration
- Session exists and is active
- Token not invalidated
Populates: req.user with decoded JWT claims
authorize(req, res, next)
Express middleware for validating route permissions. Must be used after authenticate.
const authorize = (req, res, next) => roditClient.authorize(req, res, next);
app.use('/api/admin', authenticate, authorize, handler);Validates: User has permission for the requested route and HTTP method
login_client(req, res)
Handle Express login requests from clients. Validates RODiT credentials and issues JWT token.
app.post('/api/login', (req, res) => roditClient.login_client(req, res));Request Body:
{
roditid: string,
timestamp: number,
roditid_base64url_signature: string
}Response:
{
message: 'Login successful',
token: 'eyJhbGci...',
requestId: '01HQXYZ...'
}logout_client(req, res)
Handle Express logout requests. Closes session and invalidates JWT token.
app.post('/api/logout', authenticate, (req, res) => {
return roditClient.logout_client(req, res);
});Response:
{
message: 'Logout successful',
terminationToken: 'eyJhbGci...', // Short-lived token
requestId: '01HQXYZ...'
}login_portal(configObject, port)
Authenticate to RODiT portal for server-to-server operations.
const configObject = await roditClient.getConfigOwnRodit();
const result = await roditClient.login_portal(configObject, 8443);Returns: Promise<Object> - Portal authentication result
login_server(options)
Authenticate this server to another RODiT server.
const result = await roditClient.login_server({
serverUrl: 'https://api.example.com',
credentials: {...}
});Returns: Promise<Object> - Authentication result with token
logout_server()
Logout from server-to-server session.
const result = await roditClient.logout_server();Returns: Promise<Object> - Logout result with session closure status
getConfigOwnRodit()
Get the complete RODiT configuration including token metadata.
const configObject = await roditClient.getConfigOwnRodit();
const metadata = configObject.own_rodit.metadata;
const tokenId = configObject.own_rodit.token_id;Returns: Promise<Object> - Complete RODiT configuration
Structure:
{
own_rodit: {
token_id: string,
metadata: {
jwt_duration: number,
max_requests: string,
maxrq_window: string,
permissioned_routes: string, // JSON string
subjectuniqueidentifier_url: string,
webhook_url: string,
// ... other metadata fields
}
},
port: number
}isOperationPermitted(method, path)
Check if an operation is permitted based on token permissions.
const hasPermission = roditClient.isOperationPermitted('POST', '/api/admin/users');
if (!hasPermission) {
return res.status(403).json({ error: 'Forbidden' });
}Parameters:
method(string): HTTP methodpath(string): API path
Returns: boolean
getStateManager()
Get the authentication state manager.
const stateManager = roditClient.getStateManager();Returns: AuthStateManager instance
getRoditManager()
Get the RODiT manager for credential operations.
const roditManager = roditClient.getRoditManager();
const credentials = await roditManager.getCredentials('server');Returns: RoditManager instance
getSessionManager()
Get the session manager.
const sessionManager = roditClient.getSessionManager();
const activeCount = await sessionManager.getActiveSessionCount();Returns: SessionManager instance
getLogger()
Get the logger instance.
const logger = roditClient.getLogger();
logger.info('Message', { component: 'MyComponent' });Returns: Logger instance
getLoggingMiddleware()
Get the logging middleware.
const loggingmw = roditClient.getLoggingMiddleware();
app.use(loggingmw);Returns: Express middleware function
getRateLimitMiddleware()
Get the rate limiting middleware factory.
const ratelimitmw = roditClient.getRateLimitMiddleware();
const limiter = ratelimitmw(100, 900); // 100 requests per 15 minutes
app.use(limiter);Parameters:
maxRequests(number): Maximum requests allowedwindowSeconds(number): Time window in seconds
Returns: Express middleware function
getPerformanceService()
Get the performance tracking service.
const performanceService = roditClient.getPerformanceService();
performanceService.recordRequest(req);
performanceService.recordMetric('operation_duration', 150, { operation: 'db_query' });Returns: PerformanceService instance
getConfig()
Get the configuration service.
const config = roditClient.getConfig();
const dbPath = config.get('API_DEFAULT_OPTIONS.DB_PATH');Returns: Config instance
getWebhookHandler()
Get the webhook handler.
const webhookHandler = roditClient.getWebhookHandler();Returns: WebhookHandler instance
send_webhook(payload, req)
Send a webhook notification.
const result = await roditClient.send_webhook({
event: 'user_action',
data: { userId: '123', action: 'login' },
isError: false
}, req);Parameters:
payload(Object): Webhook payloadevent(string): Event namedata(Object): Event dataisError(boolean): Whether this is an error event
req(Object): Express request object (optional)
Returns: Promise<Object> - { success: boolean, ... }
Exported Components
The SDK exports these components for direct use:
const {
RoditClient, // Main client class
logger, // Logger instance
stateManager, // Authentication state manager
roditManager, // RODiT credential manager
sessionManager, // Session manager
setExpressSessionStore, // Configure session storage
configureStorageFromConfig, // Auto-configure storage
createExpressSessionMiddleware, // Create session middleware
InMemorySessionStorage, // Default storage class
SessionManager, // SessionManager facade
blockchainService, // Blockchain operations
utils, // Utility functions
config, // Configuration service
performanceService, // Performance tracking
authenticate_apicall, // Authentication middleware
login_client, // Login handler
logout_client, // Logout handler
login_client_withnep413, // NEP-413 login
login_portal, // Portal authentication
login_server, // Server authentication
logout_server, // Server logout
validate_jwt_token_be, // JWT validation
generate_jwt_token, // JWT generation
validatepermissions, // Permission middleware
webhookHandler, // Webhook handler
versioningMiddleware, // API versioning
loggingmw, // Logging middleware
ratelimitmw, // Rate limiting middleware
versionManager, // Version manager
VersionManager // Version manager class
} = require('@rodit/rodit-auth-be');RODiT Token Metadata Fields
When you call roditClient.getConfigOwnRodit(), you get access to these metadata fields:
| Field | Type | Description |
|-------|------|-------------|
| token_id | string | Unique RODiT token identifier |
| allowed_cidr | string | Permitted IP address ranges (CIDR format) |
| allowed_iso3166list | string | Geographic restrictions (JSON string) |
| jwt_duration | number | JWT token lifetime in seconds |
| max_requests | string | Rate limit - maximum requests per window |
| maxrq_window | string | Rate limit - time window in seconds |
| not_before | string | Token validity start date (ISO format) |
| not_after | string | Token validity end date (ISO format) |
| openapijson_url | string | OpenAPI specification URL |
| permissioned_routes | string | Allowed API routes and methods (JSON string) |
| serviceprovider_id | string | Blockchain contract and service provider info |
| serviceprovider_signature | string | Cryptographic signature for verification |
| subjectuniqueidentifier_url | string | Primary API service endpoint |
| userselected_dn | string | User-selected display name |
| webhook_cidr | string | Allowed IP ranges for webhooks |
| webhook_url | string | Webhook endpoint URL |
Best Practices
1. Single Client Initialization
Always initialize the RoditClient once in your main application file:
// ✅ Good - Single initialization
async function startServer() {
const roditClient = await RoditClient.create('server');
app.locals.roditClient = roditClient;
// Mount protected routes AFTER client initialization
const authenticate = (req, res, next) => roditClient.authenticate(req, res, next);
const authorize = (req, res, next) => roditClient.authorize(req, res, next);
app.use('/api/echo', authenticate, echoRoutes);
app.use('/api/cruda', authenticate, authorize, crudaRoutes);
// ... rest of server setup
}
// ❌ Bad - Multiple initializations
app.get('/route1', async (req, res) => {
const client = await RoditClient.create('server'); // Don't do this
});2. Use App.locals for Shared Access
Store the client in app.locals for access across all routes:
// ✅ Good - Shared instance via app.locals
const router = express.Router();
router.get('/data', (req, res) => {
const client = req.app.locals.roditClient;
const logger = client.getLogger();
logger.info('Processing request', {
component: 'DataRoute',
userId: req.user?.id,
requestId: req.requestId
});
res.json({ data: 'example' });
});
// ❌ Bad - Creating new instances in routes
const { RoditClient } = require('@rodit/rodit-auth-be');
const client = new RoditClient(); // Don't do this in routes3. Proper Error Handling
Always wrap SDK operations in try-catch blocks and include request context:
// ✅ Good - Comprehensive error handling
app.get('/api/data', authenticate, async (req, res) => {
const startTime = Date.now();
const client = req.app.locals.roditClient;
const logger = client.getLogger();
try {
const data = await processData(req.user.id);
logger.infoWithContext('Request successful', {
component: 'API',
method: 'getData',
userId: req.user.id,
requestId: req.requestId,
duration: Date.now() - startTime
});
res.json({ data, requestId: req.requestId });
} catch (error) {
logger.errorWithContext('Request failed', {
component: 'API',
