Authentication Flow Overview
The vendor authentication flow enables businesses to authenticate users through palm vein scanning without handling biometric data directly.
Implementation Steps
Step 1: Establish WebSocket Connection
class VendorAuthClient {
private ws: WebSocket;
private authCallbacks: Map<string, (result: any) => void>;
async connect() {
this.ws = new WebSocket('wss://api.boop.network/vendor/connect', {
headers: {
'X-Vendor-Id': process.env.VENDOR_ID,
'X-API-Key': process.env.VENDOR_API_KEY
}
});
this.ws.on('open', () => {
console.log('Connected to BOOP Network');
this.sendHeartbeat();
});
this.ws.on('message', (data) => {
const message = JSON.parse(data.toString());
this.handleMessage(message);
});
this.ws.on('error', (error) => {
console.error('WebSocket error:', error);
this.reconnect();
});
}
private sendHeartbeat() {
setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
}
private async reconnect() {
await new Promise(resolve => setTimeout(resolve, 5000));
this.connect();
}
}
Step 2: Create Authentication Context
Authentication contexts define what you need from the user:
Payment
Entrance
Membership
{
"type": "create_auth_context",
"context_id": "ctx_unique_id",
"auth_type": "payment",
"requirements": {
"amount": 2500, // Amount in cents
"currency": "USD",
"merchant_name": "Coffee Shop",
"terminal_id": "term_001",
"attributes_required": ["email"],
"attributes_optional": ["phone", "loyalty_id"]
},
"timeout": 30000, // 30 seconds
"timestamp": "2024-01-15T10:00:00Z"
}
{
"type": "create_auth_context",
"context_id": "ctx_unique_id",
"auth_type": "entrance",
"requirements": {
"venue_id": "venue_123",
"event_id": "event_456",
"entrance_type": "main",
"require_age_verification": true,
"minimum_age": 21,
"attributes_required": ["age_verified"],
"attributes_optional": ["membership_status"]
},
"timeout": 15000,
"timestamp": "2024-01-15T20:00:00Z"
}
{
"type": "create_auth_context",
"context_id": "ctx_unique_id",
"auth_type": "membership",
"requirements": {
"action": "verify_status",
"membership_types": ["gold", "platinum", "vip"],
"benefits_requested": ["lounge_access", "priority_checkout"],
"attributes_required": ["membership_tier", "member_since"],
"attributes_optional": ["email", "preferences"]
},
"timeout": 20000,
"timestamp": "2024-01-15T14:00:00Z"
}
Step 3: Handle Authentication Result
interface AuthResult {
type: 'auth_result';
context_id: string;
success: boolean;
user_id?: string;
attributes?: Record<string, any>;
error?: {
code: string;
message: string;
};
metadata: {
palm_side: 'left' | 'right';
quality_score: number;
timestamp: string;
};
}
class TransactionHandler {
async processPayment(amount: number): Promise<AuthResult> {
const contextId = crypto.randomUUID();
// Create auth context
const context = {
type: 'create_auth_context',
context_id: contextId,
auth_type: 'payment',
requirements: {
amount: amount,
currency: 'USD',
attributes_required: ['email']
}
};
// Send context
await this.vendorClient.send(context);
// Wait for result
return new Promise((resolve, reject) => {
this.vendorClient.onAuthResult(contextId, (result) => {
if (result.success) {
this.completeTransaction(result);
resolve(result);
} else {
reject(new Error(result.error?.message));
}
});
// Timeout after 30 seconds
setTimeout(() => {
reject(new Error('Authentication timeout'));
}, 30000);
});
}
private completeTransaction(result: AuthResult) {
// Process successful authentication
console.log(`Payment authorized for user: ${result.user_id}`);
console.log(`Email for receipt: ${result.attributes?.email}`);
// Record transaction
this.recordTransaction({
user_id: result.user_id,
amount: this.currentAmount,
timestamp: result.metadata.timestamp,
quality_score: result.metadata.quality_score
});
}
}
Error Handling
Common Error Codes
| Code | Description | Action |
|---|
AUTH_TIMEOUT | Authentication timed out | Retry or fallback method |
NO_MATCH | Palm not recognized | Ask user to try again |
PALM_NOT_REGISTERED | User hasn’t registered this palm | Suggest registration |
INSUFFICIENT_QUALITY | Poor scan quality | Clean scanner, retry |
CONSENT_REQUIRED | User needs to consent to attribute sharing | Request consent |
VENDOR_NOT_AUTHORIZED | Vendor lacks permission | Contact BOOP support |
RATE_LIMITED | Too many requests | Implement backoff |
Error Handling Implementation
class ErrorHandler {
handleAuthError(error: AuthError): void {
switch (error.code) {
case 'AUTH_TIMEOUT':
this.ui.showMessage('Authentication timed out. Please try again.');
this.offerRetry();
break;
case 'NO_MATCH':
this.ui.showMessage('Palm not recognized. Please ensure you are using your registered palm.');
this.logFailedAttempt();
break;
case 'PALM_NOT_REGISTERED':
this.ui.showMessage('This palm is not registered. Would you like to register now?');
this.offerRegistration();
break;
case 'INSUFFICIENT_QUALITY':
this.ui.showMessage('Unable to read palm clearly. Please clean the scanner and try again.');
this.checkScannerStatus();
break;
case 'CONSENT_REQUIRED':
this.ui.showMessage('Additional permissions needed. Please grant access to continue.');
this.requestConsent(error.details.attributes);
break;
default:
this.ui.showMessage('Authentication failed. Please try another method.');
this.offerFallback();
}
}
}
Advanced Features
Batch Authentication
For high-volume scenarios:
class BatchAuthProcessor {
async processBatch(requests: AuthRequest[]): Promise<AuthResult[]> {
const contexts = requests.map(req => ({
type: 'create_auth_context',
context_id: crypto.randomUUID(),
auth_type: req.type,
requirements: req.requirements,
batch_id: this.currentBatchId
}));
// Send all contexts
await Promise.all(contexts.map(ctx => this.send(ctx)));
// Collect results
return Promise.all(
contexts.map(ctx => this.waitForResult(ctx.context_id))
);
}
}
Risk Scoring
Implement risk-based authentication:
interface RiskFactors {
time_since_last_auth: number;
location_change: boolean;
amount_unusual: boolean;
device_trusted: boolean;
}
function calculateRiskScore(factors: RiskFactors): number {
let score = 0;
if (factors.time_since_last_auth > 86400000) score += 10; // >1 day
if (factors.location_change) score += 20;
if (factors.amount_unusual) score += 30;
if (!factors.device_trusted) score += 15;
return score;
}
async function riskBasedAuth(request: AuthRequest): Promise<void> {
const risk = calculateRiskScore(request.riskFactors);
if (risk < 20) {
// Low risk - standard auth
await standardAuth(request);
} else if (risk < 50) {
// Medium risk - require additional verification
await enhancedAuth(request, ['email', 'phone']);
} else {
// High risk - multi-factor required
await multiFactorAuth(request);
}
}
Session Management
Maintain authentication sessions:
class AuthSession {
private sessions: Map<string, SessionData> = new Map();
createSession(userId: string, attributes: any): string {
const sessionId = crypto.randomUUID();
this.sessions.set(sessionId, {
userId,
attributes,
createdAt: Date.now(),
lastActivity: Date.now(),
authCount: 0
});
// Expire after 1 hour of inactivity
setTimeout(() => {
this.expireSession(sessionId);
}, 3600000);
return sessionId;
}
async reauthenticate(sessionId: string): Promise<boolean> {
const session = this.sessions.get(sessionId);
if (!session) return false;
// Check if re-auth needed
const timeSinceAuth = Date.now() - session.lastActivity;
if (timeSinceAuth > 600000) { // 10 minutes
// Quick re-auth (palm only, no attributes)
const result = await this.quickAuth(session.userId);
if (result.success) {
session.lastActivity = Date.now();
session.authCount++;
return true;
}
}
return false;
}
}
Connection Pooling
class ConnectionPool {
private connections: WebSocket[] = [];
private maxConnections = 5;
private currentIndex = 0;
async initialize() {
for (let i = 0; i < this.maxConnections; i++) {
const ws = await this.createConnection();
this.connections.push(ws);
}
}
getConnection(): WebSocket {
const ws = this.connections[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.maxConnections;
return ws;
}
}
Caching Strategy
class AttributeCache {
private cache: Map<string, CachedAttributes> = new Map();
set(userId: string, attributes: any, ttl: number = 300000) {
this.cache.set(userId, {
data: attributes,
expiry: Date.now() + ttl
});
}
get(userId: string): any | null {
const cached = this.cache.get(userId);
if (cached && cached.expiry > Date.now()) {
return cached.data;
}
this.cache.delete(userId);
return null;
}
}
Testing
Unit Tests
describe('VendorAuthentication', () => {
let client: VendorAuthClient;
let mockWs: MockWebSocket;
beforeEach(() => {
mockWs = new MockWebSocket();
client = new VendorAuthClient(mockWs);
});
test('successful payment authentication', async () => {
const result = await client.processPayment(1000);
expect(result.success).toBe(true);
expect(result.user_id).toBeDefined();
expect(result.attributes.email).toBeDefined();
});
test('handles timeout correctly', async () => {
mockWs.simulateTimeout();
await expect(client.processPayment(1000))
.rejects.toThrow('Authentication timeout');
});
test('retries on connection loss', async () => {
mockWs.simulateDisconnect();
await wait(100);
expect(mockWs.reconnectAttempts).toBe(1);
});
});
Security Best Practices
Never store or log complete authentication responses containing user attributes. Always redact sensitive information.
Secure Implementation Checklist
Next Steps