UnlockOS Developers
← Back to blog
🔐

Building Secure Guest Access: Multi-Channel Authentication

Mar 23, 2026Mar 29, 2026
6 min
204 commits
Depth 8/10
securityauthenticationauthorizationtypescriptapi-design

Building Secure Guest Access: Multi-Channel Authentication in Smart Lock Systems

Introduction

Security-critical systems require robust authentication mechanisms that can handle multiple access channels while maintaining strict security boundaries. When building smart lock management systems, the challenge becomes even more complex as you need to verify guest identity across various platforms - web applications, mobile apps, messaging platforms like LINE, and third-party booking systems.

This article explores how to implement a secure multi-channel authentication system that maintains data integrity while providing seamless guest experiences across different touchpoints.

Authentication Layer Architecture

A robust authentication system requires multiple security layers with clear boundaries between each access method:

// Core authentication interface
export interface AuthenticationContext {
  userId: string;
  facilityId: string;
  channel: 'web' | 'line' | 'api' | 'booking';
  permissions: Permission[];
  sessionExpiry: Date;
}

// Channel-specific authentication
export interface ChannelAuth {
  validateRequest(request: AuthRequest): Promise<AuthResult>;
  refreshToken(token: string): Promise<AuthResult>;
  revokeAccess(sessionId: string): Promise<void>;
}

Secure Token Management

Each authentication channel requires different token handling strategies while maintaining security consistency:

// JWT-based session management
export class SecureTokenManager {
  private readonly JWT_SECRET: string;
  private readonly TOKEN_EXPIRY = '15m';
  private readonly REFRESH_EXPIRY = '7d';

  async generateAccessToken(context: AuthenticationContext): Promise<string> {
    const payload = {
      sub: context.userId,
      fid: context.facilityId,
      chn: context.channel,
      perms: context.permissions.map(p => p.code),
      exp: Math.floor(Date.now() / 1000) + (15 * 60) // 15 minutes
    };
    
    return jwt.sign(payload, this.JWT_SECRET, { algorithm: 'HS256' });
  }

  async validateToken(token: string): Promise<AuthenticationContext | null> {
    try {
      const decoded = jwt.verify(token, this.JWT_SECRET) as JWTPayload;
      
      // Additional validation layers
      if (await this.isTokenRevoked(token)) {
        return null;
      }
      
      return this.reconstructContext(decoded);
    } catch (error) {
      // Log security events without exposing internal details
      this.auditLog.record('token_validation_failed', { 
        error: 'invalid_token',
        timestamp: new Date()
      });
      return null;
    }
  }
}

Channel-Specific Security Implementation

LINE Integration Security

Messaging platform integrations require special attention to prevent spoofing and ensure user verification:

export class LineAuthenticationHandler implements ChannelAuth {
  async validateRequest(request: AuthRequest): Promise<AuthResult> {
    // Verify LINE signature to prevent request forgery
    const signature = request.headers['x-line-signature'];
    const body = request.body;
    
    if (!this.verifyLineSignature(signature, body)) {
      throw new SecurityError('Invalid LINE signature');
    }
    
    // Extract and validate user context
    const lineUserId = this.extractLineUserId(body);
    const guestMapping = await this.findGuestMapping(lineUserId);
    
    if (!guestMapping || !this.isWithinAccessWindow(guestMapping)) {
      return { success: false, reason: 'unauthorized_access' };
    }
    
    return {
      success: true,
      context: {
        userId: guestMapping.guestId,
        facilityId: guestMapping.facilityId,
        channel: 'line',
        permissions: this.resolveGuestPermissions(guestMapping),
        sessionExpiry: guestMapping.checkoutTime
      }
    };
  }
  
  private verifyLineSignature(signature: string, body: string): boolean {
    const hash = crypto
      .createHmac('sha256', this.channelSecret)
      .update(body)
      .digest('base64');
    
    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(`sha256=${hash}`)
    );
  }
}

API Security Layers

API endpoints require tiered authentication based on access sensitivity:

// Three-tier API security
export enum SecurityLevel {
  PUBLIC = 1,     // No auth required (facility info)
  GUEST = 2,      // Guest authentication required
  FACILITY = 3    // Facility API key required
}

export function withApiKeyAuth(level: SecurityLevel) {
  return async (req: Request): Promise<Response> => {
    try {
      switch (level) {
        case SecurityLevel.FACILITY:
          const apiKey = req.headers.get('x-api-key');
          if (!apiKey || !await this.validateFacilityApiKey(apiKey)) {
            return this.unauthorizedResponse('Invalid API key');
          }
          break;
          
        case SecurityLevel.GUEST:
          const authHeader = req.headers.get('authorization');
          const token = this.extractBearerToken(authHeader);
          
          const context = await this.tokenManager.validateToken(token);
          if (!context) {
            return this.unauthorizedResponse('Invalid or expired token');
          }
          
          // Attach context to request for downstream use
          req.authContext = context;
          break;
      }
      
      return await this.handleRequest(req);
    } catch (error) {
      // Never expose internal error details
      return this.errorResponse('Authentication failed');
    }
  };
}

Rate Limiting and Abuse Prevention

Security-critical systems must protect against abuse while maintaining legitimate access:

export class RateLimitManager {
  private readonly limits = new Map<string, RateLimit>();
  
  async checkRateLimit(
    identifier: string, 
    action: string, 
    context: AuthenticationContext
  ): Promise<boolean> {
    const key = `${context.channel}:${identifier}:${action}`;
    const limit = this.getLimitForAction(action, context.channel);
    
    const current = await this.redis.get(key);
    const count = current ? parseInt(current) : 0;
    
    if (count >= limit.maxAttempts) {
      // Log potential abuse
      this.auditLog.record('rate_limit_exceeded', {
        identifier,
        action,
        channel: context.channel,
        currentCount: count,
        timestamp: new Date()
      });
      return false;
    }
    
    // Increment counter with expiry
    await this.redis.setex(key, limit.windowSeconds, count + 1);
    return true;
  }
  
  private getLimitForAction(action: string, channel: string): RateLimit {
    // Different limits per channel and action
    const limits = {
      'line:access_key': { maxAttempts: 5, windowSeconds: 300 },
      'api:create_reservation': { maxAttempts: 10, windowSeconds: 60 },
      'web:login_attempt': { maxAttempts: 3, windowSeconds: 900 }
    };
    
    const key = `${channel}:${action}`;
    return limits[key] || { maxAttempts: 1, windowSeconds: 60 };
  }
}

Audit Logging for Security Events

Comprehensive audit trails are essential for security monitoring and compliance:

export interface SecurityAuditEvent {
  eventType: 'authentication' | 'authorization' | 'access_denied' | 'suspicious_activity';
  userId?: string;
  facilityId?: string;
  channel: string;
  ipAddress?: string;
  userAgent?: string;
  details: Record<string, any>;
  timestamp: Date;
  severity: 'low' | 'medium' | 'high' | 'critical';
}

export class SecurityAuditLogger {
  async logEvent(event: SecurityAuditEvent): Promise<void> {
    // Structure logs for security analysis
    const logEntry = {
      '@timestamp': event.timestamp.toISOString(),
      event_type: event.eventType,
      severity: event.severity,
      channel: event.channel,
      facility_id: event.facilityId,
      // Hash PII for privacy while maintaining traceability
      user_hash: event.userId ? this.hashPII(event.userId) : undefined,
      ip_hash: event.ipAddress ? this.hashPII(event.ipAddress) : undefined,
      details: this.sanitizeDetails(event.details)
    };
    
    // Write to security-specific log stream
    await this.securityLogger.write(logEntry);
    
    // Alert on critical events
    if (event.severity === 'critical') {
      await this.alertManager.sendAlert(
        'Critical security event detected',
        logEntry
      );
    }
  }
  
  private sanitizeDetails(details: Record<string, any>): Record<string, any> {
    // Remove sensitive data while preserving security-relevant information
    const sanitized = { ...details };
    delete sanitized.password;
    delete sanitized.apiKey;
    delete sanitized.token;
    
    return sanitized;
  }
}

Error Handling Without Information Disclosure

Security systems must fail securely without revealing system internals:

export class SecureErrorHandler {
  handleAuthenticationError(error: Error, context: RequestContext): Response {
    // Log detailed error internally
    this.logger.error('Authentication failed', {
      error: error.message,
      stack: error.stack,
      requestId: context.requestId,
      timestamp: new Date()
    });
    
    // Return generic error to client
    const publicError = this.sanitizeError(error);
    
    return new Response(JSON.stringify({
      success: false,
      error: publicError.message,
      code: publicError.code
    }), {
      status: publicError.status,
      headers: {
        'Content-Type': 'application/json',
        // Security headers
        'X-Content-Type-Options': 'nosniff',
        'X-Frame-Options': 'DENY'
      }
    });
  }
  
  private sanitizeError(error: Error): PublicError {
    // Map internal errors to safe public messages
    const errorMap = {
      'TokenExpiredError': {
        message: 'Session expired',
        code: 'TOKEN_EXPIRED',
        status: 401
      },
      'DatabaseConnectionError': {
        message: 'Service temporarily unavailable',
        code: 'SERVICE_ERROR',
        status: 503
      }
    };
    
    const mapped = errorMap[error.constructor.name];
    return mapped || {
      message: 'Authentication failed',
      code: 'AUTH_ERROR',
      status: 401
    };
  }
}

Testing Security Implementation

Security features require comprehensive testing to ensure they work under attack conditions:

describe('Multi-Channel Authentication Security', () => {
  describe('Token Validation', () => {
    it('should reject expired tokens', async () => {
      const expiredToken = await createExpiredToken();
      const result = await tokenManager.validateToken(expiredToken);
      expect(result).toBeNull();
    });
    
    it('should reject tampered tokens', async () => {
      const validToken = await createValidToken();
      const tamperedToken = validToken.slice(0, -5) + 'XXXXX';
      const result = await tokenManager.validateToken(tamperedToken);
      expect(result).toBeNull();
    });
  });
  
  describe('Rate Limiting', () => {
    it('should block after exceeding rate limit', async () => {
      const rateLimiter = new RateLimitManager();
      
      // Exceed rate limit
      for (let i = 0; i < 6; i++) {
        await rateLimiter.checkRateLimit('test-user', 'access_key', context);
      }
      
      const blocked = await rateLimiter.checkRateLimit('test-user', 'access_key', context);
      expect(blocked).toBe(false);
    });
  });
  
  describe('Channel Security', () => {
    it('should verify LINE webhook signatures', async () => {
      const handler = new LineAuthenticationHandler();
      const invalidRequest = createInvalidLineRequest();
      
      await expect(handler.validateRequest(invalidRequest))
        .rejects.toThrow('Invalid LINE signature');
    });
  });
});

Summary

Building secure multi-channel authentication for smart lock systems requires:

  1. Layered Security Architecture: Clear boundaries between authentication channels with consistent security policies
  2. Robust Token Management: Short-lived tokens with secure validation and revocation capabilities
  3. Channel-Specific Validation: Each integration point requires tailored security measures
  4. Comprehensive Audit Logging: Security events must be logged for monitoring and compliance
  5. Secure Error Handling: Never expose system internals through error messages
  6. Thorough Security Testing: Test authentication under attack conditions

The key insight is that security-critical systems must assume hostile environments and design every authentication touchpoint with security-first principles, while maintaining usability across different user channels.

Key Insights

1
Security

Multi-Layer Authentication

Implement tiered security levels (public, guest, facility) with channel-specific validation to prevent unauthorized access while maintaining usability

2
Security

Signature Verification

Use cryptographic signature validation for webhook integrations to prevent request forgery and ensure authentic communication

3
Reliability

Rate Limiting Strategy

Implement context-aware rate limiting with different thresholds per channel and action type to prevent abuse without blocking legitimate users

4
Security

Audit Trail Design

Structure security logs with PII hashing and severity levels for effective monitoring while maintaining privacy compliance