UnlockOS Developers
← Back to blog
🛡️

Robust Authentication & Error Handling in Production Systems

Dec 22, 2025Dec 28, 2025
6 min
46 commits
Depth 8/10
securityauthenticationerror-handlingtypescript

Robust Authentication & Error Handling in Production Systems

Introduction

Building security-critical systems requires meticulous attention to authentication flows and error handling patterns. Recent production incidents have highlighted the importance of robust authentication mechanisms and graceful error recovery. This article explores key patterns for building trustworthy systems through proper authentication handling, role-based access control, and defensive programming practices.

Authentication State Management

Authentication in distributed systems requires careful state management to prevent unauthorized access while maintaining system usability.

OTP Authentication Implementation

interface AuthState {
  step: 'initial' | 'otp-sent' | 'otp-verified' | 'authenticated';
  userId?: string;
  sessionToken?: string;
  expiresAt?: Date;
}

class AuthenticationManager {
  private state: AuthState = { step: 'initial' };

  async initiateOTPLogin(email: string): Promise<void> {
    try {
      await this.sendOTP(email);
      this.state = { step: 'otp-sent' };
    } catch (error) {
      this.handleAuthError(error);
      throw new AuthenticationError('Failed to send OTP');
    }
  }

  async verifyOTP(code: string): Promise<void> {
    if (this.state.step !== 'otp-sent') {
      throw new InvalidStateError('OTP verification not available');
    }
    
    const isValid = await this.validateOTP(code);
    if (!isValid) {
      throw new AuthenticationError('Invalid OTP code');
    }
    
    this.state = { step: 'otp-verified' };
  }
}

Graceful API Authentication Recovery

When dealing with authentication failures, systems should implement fallback mechanisms:

class APIClient {
  async callWithFallback<T>(endpoint: string, data: any): Promise<T> {
    try {
      // Primary: Use Edge Function
      return await this.callEdgeFunction(endpoint, data);
    } catch (error) {
      if (this.isAuthError(error)) {
        // Fallback: Direct API call with fresh credentials
        const freshToken = await this.refreshAuthentication();
        return await this.callDirectAPI(endpoint, data, freshToken);
      }
      throw error;
    }
  }

  private isAuthError(error: any): boolean {
    return error.status === 401 || error.status === 403;
  }
}

Role-Based Access Control

Proper role management prevents privilege escalation and ensures users only access authorized resources.

Dynamic Role Level Checking

interface Role {
  id: string;
  name: string;
  level: number; // Higher numbers = more privileges
  permissions: Permission[];
}

class RoleManager {
  async checkAccess(userId: string, requiredLevel: number): Promise<boolean> {
    try {
      const userRole = await this.getUserRole(userId);
      return userRole.level >= requiredLevel;
    } catch (error) {
      // Fail closed - deny access on error
      this.logger.error('Role check failed', { userId, error });
      return false;
    }
  }

  async saveRolePermissions(roleId: string, permissions: Permission[]): Promise<void> {
    const transaction = await this.db.transaction();
    
    try {
      // Use maybeSingle() for queries that might return no results
      const existingRole = await transaction
        .from('roles')
        .select('*')
        .eq('id', roleId)
        .maybeSingle();

      if (!existingRole) {
        throw new NotFoundError(`Role ${roleId} not found`);
      }

      await transaction
        .from('role_permissions')
        .delete()
        .eq('role_id', roleId);

      await transaction
        .from('role_permissions')
        .insert(permissions.map(p => ({ role_id: roleId, permission_id: p.id })));

      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }
}

Database Query Safety

Proper query handling prevents runtime errors and ensures data consistency.

Safe Query Patterns

class DatabaseService {
  // Use .maybeSingle() when record might not exist
  async findUserRole(userId: string): Promise<Role | null> {
    const result = await this.db
      .from('user_roles')
      .select('*')
      .eq('user_id', userId)
      .maybeSingle(); // Returns null if no record found
    
    return result;
  }

  // Use .single() only when record MUST exist
  async getUserById(userId: string): Promise<User> {
    const result = await this.db
      .from('users')
      .select('*')
      .eq('id', userId)
      .single(); // Throws if no record found
    
    if (!result) {
      throw new NotFoundError(`User ${userId} not found`);
    }
    
    return result;
  }
}

Initialization Safety

Systems must handle startup conditions gracefully, especially in distributed environments.

Safe Database Initialization

class DatabaseInitializer {
  private isInitialized = false;
  private initializationPromise?: Promise<void>;

  async ensureInitialized(): Promise<void> {
    if (this.isInitialized) {
      return;
    }

    if (this.initializationPromise) {
      return this.initializationPromise;
    }

    this.initializationPromise = this.initialize();
    return this.initializationPromise;
  }

  private async initialize(): Promise<void> {
    try {
      await this.runMigrations();
      await this.seedRequiredData();
      await this.validateSystemHealth();
      
      this.isInitialized = true;
    } catch (error) {
      this.initializationPromise = undefined;
      throw new InitializationError('Database initialization failed', error);
    }
  }

  private async validateSystemHealth(): Promise<void> {
    const healthCheck = await this.db.raw('SELECT 1 as health');
    if (!healthCheck) {
      throw new Error('Database health check failed');
    }
  }
}

Payment Security Patterns

Critical operations like payments require additional safety measures.

State-Driven Payment Flow

interface PaymentState {
  cardComplete: boolean;
  processing: boolean;
  validated: boolean;
}

class PaymentManager {
  private state: PaymentState = {
    cardComplete: false,
    processing: false,
    validated: false
  };

  updateCardInformation(cardData: Partial<CardInfo>): void {
    const isComplete = this.validateCardCompleteness(cardData);
    this.state.cardComplete = isComplete;
    this.state.validated = isComplete && this.validateCardData(cardData);
  }

  canProcessPayment(): boolean {
    return this.state.cardComplete && 
           this.state.validated && 
           !this.state.processing;
  }

  async processPayment(): Promise<void> {
    if (!this.canProcessPayment()) {
      throw new InvalidStateError('Payment cannot be processed');
    }

    this.state.processing = true;
    try {
      await this.chargeCard();
    } finally {
      this.state.processing = false;
    }
  }
}

Error Classification and Recovery

Different error types require different handling strategies:

class ErrorHandler {
  handleAPIError(error: APIError): void {
    switch (error.status) {
      case 401:
        // Authentication failed - trigger re-auth
        this.triggerReAuthentication();
        break;
      case 403:
        // Authorization failed - log security event
        this.logSecurityEvent('unauthorized_access_attempt', error);
        break;
      case 406:
        // Not acceptable - likely query issue, use fallback
        this.useFallbackQuery();
        break;
      default:
        // Unknown error - fail safe
        this.escalateError(error);
    }
  }

  private logSecurityEvent(event: string, context: any): void {
    // Audit logging for security events
    this.auditLogger.log({
      event,
      timestamp: new Date().toISOString(),
      context,
      severity: 'high'
    });
  }
}

Summary

Building trustworthy systems requires:

  1. Defensive Authentication: Multiple fallback mechanisms and proper state management
  2. Role-Based Security: Dynamic level checking and fail-closed access control
  3. Safe Database Operations: Using appropriate query methods and transaction safety
  4. Initialization Safety: Graceful startup handling and health validation
  5. Payment Security: State-driven flows and validation gates
  6. Error Recovery: Classified error handling with appropriate fallbacks

These patterns ensure systems remain secure and reliable even when facing unexpected conditions or failures.

Key Insights

1
Security

Multi-layer Authentication Fallbacks

Implementing fallback mechanisms for authentication failures prevents system lockouts while maintaining security

2
Error Handling

Fail-Closed Access Control

When role checks fail, deny access by default rather than allowing potentially unauthorized operations

3
Database Safety

Query Method Selection

Using .maybeSingle() vs .single() prevents 406 errors and handles optional data gracefully

4
State Management

Payment Flow Validation

State-driven payment processing ensures all prerequisites are met before executing critical operations