Skip to main content

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:
{
  "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"
}

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

CodeDescriptionAction
AUTH_TIMEOUTAuthentication timed outRetry or fallback method
NO_MATCHPalm not recognizedAsk user to try again
PALM_NOT_REGISTEREDUser hasn’t registered this palmSuggest registration
INSUFFICIENT_QUALITYPoor scan qualityClean scanner, retry
CONSENT_REQUIREDUser needs to consent to attribute sharingRequest consent
VENDOR_NOT_AUTHORIZEDVendor lacks permissionContact BOOP support
RATE_LIMITEDToo many requestsImplement 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;
  }
}

Performance Optimization

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

  • Use TLS 1.3 for all connections
  • Validate vendor credentials on each request
  • Implement rate limiting (max 100 req/min)
  • Log all authentication attempts
  • Encrypt cached attributes
  • Implement request signing
  • Use secure random for context IDs
  • Set appropriate timeouts
  • Handle connection failures gracefully
  • Regular security audits

Next Steps