UnlockOS Developers
← Back to blog
🛡️

Building Trust with Better UX Error Handling in Access Control

Jan 12, 2026Jan 18, 2026
4 min
56 commits
Depth 6/10
error-handlingsecurityuser-experiencetypescript

Building Trust with Better UX Error Handling in Access Control

Introduction

In security-critical systems like smart lock management, how you handle and display errors can significantly impact both security and user trust. Recent improvements to UnlockOS SDK demonstrate a crucial principle: user-friendly error handling doesn't compromise security—it enhances it by reducing user frustration and preventing workarounds.

The Security-UX Balance in Error Handling

Traditional security systems often display cryptic error messages to "hide implementation details." However, this approach can backfire:

  • Users become frustrated and seek workarounds
  • Support burden increases
  • System adoption decreases
  • Users lose trust in the system's reliability

Implementing Gentle Error Recovery

The key is implementing error handling that's informative without being exploitable:

interface UserFriendlyError {
  code: string;
  userMessage: string;
  technicalDetails?: string; // Only for authorized users
  recoveryActions: string[];
  severity: 'info' | 'warning' | 'error';
}

function handleOccupancyError(error: SystemError): UserFriendlyError {
  return {
    code: 'OCCUPANCY_LIMIT_REACHED',
    userMessage: 'This property has reached its maximum occupancy. Please contact support if you believe this is incorrect.',
    recoveryActions: [
      'Check your booking details',
      'Contact property management',
      'Try again in a few minutes'
    ],
    severity: 'warning'
  };
}

Internationalization for Global Trust

Error messages in users' native languages build immediate trust:

interface I18nErrorHandler {
  formatError(error: UserFriendlyError, locale: string): string;
  getRecoveryInstructions(errorCode: string, locale: string): string[];
}

const errorHandler: I18nErrorHandler = {
  formatError(error, locale) {
    const messages = {
      'en': error.userMessage,
      'ja': 'この物件は最大収容人数に達しています。間違いだと思われる場合はサポートにお問い合わせください。',
      'es': 'Esta propiedad ha alcanzado su capacidad máxima. Contacte con soporte si cree que es incorrecto.'
    };
    return messages[locale] || messages['en'];
  }
};

Progressive Error Disclosure

Show appropriate detail levels based on user context:

class ContextualErrorHandler {
  private formatForUser(error: SystemError, userRole: UserRole): DisplayError {
    const baseError = {
      message: this.getUserFriendlyMessage(error),
      timestamp: new Date(),
      referenceId: error.id
    };

    // Add technical details for authorized users
    if (userRole === 'admin' || userRole === 'developer') {
      return {
        ...baseError,
        technicalDetails: error.stack,
        systemState: error.context
      };
    }

    return baseError;
  }

  private getUserFriendlyMessage(error: SystemError): string {
    // Map system errors to user-friendly messages
    const errorMap = {
      'INVALID_ACCESS_TOKEN': 'Your access has expired. Please check in again.',
      'LOCK_COMMUNICATION_FAILED': 'Unable to connect to the lock. Please try again.',
      'PARAMETER_VALIDATION_FAILED': 'Some required information is missing. Please check your details.'
    };
    
    return errorMap[error.code] || 'Something went wrong. Our team has been notified.';
  }
}

Skeleton Loading for Perceived Reliability

While the system processes requests, skeleton loaders maintain user confidence:

interface LoadingState {
  isLoading: boolean;
  operation: string;
  estimatedDuration?: number;
}

function SkeletonLoader({ operation }: { operation: string }) {
  return (
    <div className="animate-pulse">
      <div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
      <div className="h-4 bg-gray-200 rounded w-1/2 mb-2"></div>
      <div className="text-xs text-gray-500">
        {operation === 'unlock' ? 'Communicating with lock...' : 'Processing request...'}
      </div>
    </div>
  );
}

Error Recovery Strategies

Implement automatic recovery where safe:

class ResilientApiClient {
  async executeWithRetry<T>(
    operation: () => Promise<T>,
    maxRetries: number = 3,
    backoffMs: number = 1000
  ): Promise<T> {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        if (attempt === maxRetries) {
          throw this.enhanceError(error, attempt);
        }
        
        // Only retry on recoverable errors
        if (!this.isRetryableError(error)) {
          throw error;
        }
        
        await this.delay(backoffMs * Math.pow(2, attempt - 1));
      }
    }
  }

  private isRetryableError(error: any): boolean {
    const retryableCodes = ['NETWORK_ERROR', 'TIMEOUT', 'RATE_LIMITED'];
    return retryableCodes.includes(error.code);
  }
}

Contextual Help Integration

Provide immediate assistance when errors occur:

interface ContextualHelp {
  errorCode: string;
  helpContent: string;
  videoUrl?: string;
  contactInfo?: ContactMethod;
}

function ErrorWithHelp({ error }: { error: UserFriendlyError }) {
  const helpContent = useContextualHelp(error.code);
  
  return (
    <div className="error-container">
      <div className="error-message">{error.userMessage}</div>
      
      <div className="recovery-actions">
        <h4>What you can do:</h4>
        <ul>
          {error.recoveryActions.map((action, index) => (
            <li key={index}>{action}</li>
          ))}
        </ul>
      </div>
      
      {helpContent && (
        <div className="contextual-help">
          <details>
            <summary>Need more help?</summary>
            <div dangerouslySetInnerHTML={{ __html: helpContent }} />
          </details>
        </div>
      )}
    </div>
  );
}

Summary

Effective error handling in security-critical systems requires balancing transparency with security. Key principles include:

  1. User-centric messaging: Clear, actionable error messages in the user's language
  2. Progressive disclosure: Show appropriate detail levels based on user role
  3. Graceful degradation: Skeleton loaders and retry mechanisms maintain user confidence
  4. Contextual assistance: Immediate help reduces frustration and support burden
  5. Security-aware design: Informative without exposing system vulnerabilities

By implementing these patterns, you create systems that users trust not despite their security measures, but because of how thoughtfully those measures are presented.

Key Insights

1
Security

Progressive Error Disclosure

Show appropriate error detail levels based on user role without compromising security

2
Reliability

Automatic Error Recovery

Implement retry mechanisms for recoverable errors while providing user feedback

3
User Experience

Contextual Help Integration

Provide immediate assistance and recovery actions when errors occur