Authentication Security Hardening: MFA, Biometrics & Passkeys

Account takeovers represent one of the most severe security threats to ChatGPT applications. When attackers compromise user credentials, they gain access to sensitive conversations, personal data, and business intelligence stored in chat histories. Traditional password-based authentication is no longer sufficient—credential stuffing attacks, phishing campaigns, and password database breaches have made single-factor authentication dangerously vulnerable.

Modern authentication security requires a defense-in-depth approach combining multiple verification factors, device trust mechanisms, and continuous session monitoring. Multi-factor authentication (MFA) adds verification layers beyond passwords. Biometric authentication leverages unique physical characteristics like fingerprints and facial recognition. WebAuthn passkeys provide passwordless authentication using cryptographic key pairs. Device fingerprinting establishes trust relationships with known devices. Session management enforces timeout policies and detects anomalous behavior.

This guide provides production-ready implementations for hardening ChatGPT application authentication. You'll learn to implement TOTP-based MFA, integrate biometric authenticators, deploy WebAuthn passkeys, establish device trust frameworks, and build robust session management systems. Each code example is battle-tested, includes comprehensive error handling, and follows security best practices. By implementing these authentication controls, you'll protect your users from credential-based attacks while maintaining a seamless user experience.

Multi-Factor Authentication: TOTP, SMS, and Push Notifications

Multi-factor authentication requires users to provide two or more verification factors—typically something they know (password), something they have (phone or security key), and something they are (biometric). Time-based One-Time Passwords (TOTP) generate six-digit codes that expire every 30 seconds, providing strong protection against credential replay attacks.

TOTP works by sharing a secret key between the server and the user's authenticator app (Google Authenticator, Authy, 1Password). The server and app independently generate the same code using the shared secret and the current time. SMS-based verification sends one-time codes via text message, though it's less secure due to SIM swapping attacks. Push notifications leverage mobile apps to approve or deny login attempts in real-time.

Here's a production-ready TOTP MFA implementation:

// totp-mfa-manager.ts
import * as speakeasy from 'speakeasy';
import * as QRCode from 'qrcode';
import { createHash, randomBytes } from 'crypto';

interface TOTPSecret {
  userId: string;
  secret: string;
  backupCodes: string[];
  createdAt: Date;
  verified: boolean;
}

interface MFAVerification {
  userId: string;
  token: string;
  timestamp: Date;
  successful: boolean;
  method: 'totp' | 'backup';
}

export class TOTPMFAManager {
  private readonly APP_NAME = 'MakeAIHQ ChatGPT App';
  private readonly BACKUP_CODE_COUNT = 10;
  private readonly BACKUP_CODE_LENGTH = 8;

  /**
   * Generate TOTP secret and QR code for user enrollment
   */
  async enrollUser(userId: string, email: string): Promise<{
    secret: string;
    qrCode: string;
    backupCodes: string[];
  }> {
    const secret = speakeasy.generateSecret({
      name: `${this.APP_NAME} (${email})`,
      issuer: this.APP_NAME,
      length: 32
    });

    const backupCodes = this.generateBackupCodes();

    // Generate QR code for easy enrollment
    const qrCodeDataURL = await QRCode.toDataURL(secret.otpauth_url!);

    // Store secret in database (hashed backup codes)
    const hashedBackupCodes = backupCodes.map(code =>
      this.hashBackupCode(code)
    );

    await this.storeTOTPSecret({
      userId,
      secret: secret.base32,
      backupCodes: hashedBackupCodes,
      createdAt: new Date(),
      verified: false
    });

    return {
      secret: secret.base32,
      qrCode: qrCodeDataURL,
      backupCodes
    };
  }

  /**
   * Verify TOTP token during login
   */
  async verifyTOTP(
    userId: string,
    token: string
  ): Promise<{ valid: boolean; window?: number }> {
    const totpSecret = await this.getTOTPSecret(userId);

    if (!totpSecret || !totpSecret.verified) {
      return { valid: false };
    }

    // Verify with window of ±1 period (30 seconds)
    const verified = speakeasy.totp.verify({
      secret: totpSecret.secret,
      encoding: 'base32',
      token: token,
      window: 1
    });

    // Log verification attempt
    await this.logVerification({
      userId,
      token: token.substring(0, 2) + '****',
      timestamp: new Date(),
      successful: verified,
      method: 'totp'
    });

    return { valid: verified };
  }

  /**
   * Verify backup code (one-time use)
   */
  async verifyBackupCode(userId: string, code: string): Promise<boolean> {
    const totpSecret = await this.getTOTPSecret(userId);

    if (!totpSecret) {
      return false;
    }

    const hashedCode = this.hashBackupCode(code);
    const codeIndex = totpSecret.backupCodes.indexOf(hashedCode);

    if (codeIndex === -1) {
      return false;
    }

    // Remove used backup code (one-time use)
    totpSecret.backupCodes.splice(codeIndex, 1);
    await this.updateTOTPSecret(userId, totpSecret);

    await this.logVerification({
      userId,
      token: 'backup-code',
      timestamp: new Date(),
      successful: true,
      method: 'backup'
    });

    return true;
  }

  /**
   * Generate cryptographically secure backup codes
   */
  private generateBackupCodes(): string[] {
    const codes: string[] = [];

    for (let i = 0; i < this.BACKUP_CODE_COUNT; i++) {
      const code = randomBytes(this.BACKUP_CODE_LENGTH / 2)
        .toString('hex')
        .toUpperCase()
        .match(/.{1,4}/g)!
        .join('-');

      codes.push(code);
    }

    return codes;
  }

  /**
   * Hash backup code for secure storage
   */
  private hashBackupCode(code: string): string {
    return createHash('sha256')
      .update(code.replace(/-/g, ''))
      .digest('hex');
  }

  // Database operations (implement based on your DB)
  private async storeTOTPSecret(secret: TOTPSecret): Promise<void> {
    // Store in Firestore/MongoDB
  }

  private async getTOTPSecret(userId: string): Promise<TOTPSecret | null> {
    // Retrieve from database
    return null;
  }

  private async updateTOTPSecret(
    userId: string,
    secret: TOTPSecret
  ): Promise<void> {
    // Update in database
  }

  private async logVerification(verification: MFAVerification): Promise<void> {
    // Log to security audit trail
  }
}

Biometric Authentication: FaceID, TouchID, and Fingerprints

Biometric authentication verifies user identity using unique physical characteristics—fingerprints, facial recognition, iris scans, or voice patterns. Modern smartphones and laptops include secure enclaves (Apple's Secure Enclave, Android StrongBox) that store biometric templates in hardware-isolated environments. The Web Authentication API (WebAuthn) provides standardized access to platform authenticators, enabling web applications to leverage device biometrics.

Biometric authentication offers superior user experience compared to passwords—users simply scan their face or fingerprint to authenticate. It's also more secure: biometric templates cannot be shared, written down, or phished. However, biometric data must never be transmitted or stored on servers; instead, the device's secure enclave verifies the biometric locally and returns a cryptographic assertion.

Here's a production-ready biometric authenticator:

// biometric-authenticator.ts
import { PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions } from '@simplewebauthn/typescript-types';
import { startRegistration, startAuthentication } from '@simplewebauthn/browser';

interface BiometricCredential {
  credentialId: string;
  publicKey: string;
  counter: number;
  transports?: AuthenticatorTransport[];
  createdAt: Date;
  lastUsed?: Date;
  deviceInfo: {
    userAgent: string;
    platform: string;
  };
}

export class BiometricAuthenticator {
  private readonly RP_NAME = 'MakeAIHQ ChatGPT App';
  private readonly RP_ID = 'makeaihq.com';

  /**
   * Check if biometric authentication is available
   */
  async isAvailable(): Promise<{
    available: boolean;
    authenticatorType?: string;
  }> {
    if (!window.PublicKeyCredential) {
      return { available: false };
    }

    const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();

    if (!available) {
      return { available: false };
    }

    // Detect authenticator type (platform vs roaming)
    const authenticatorType = this.detectAuthenticatorType();

    return {
      available: true,
      authenticatorType
    };
  }

  /**
   * Register biometric credential (enrollment)
   */
  async registerCredential(
    userId: string,
    username: string
  ): Promise<BiometricCredential> {
    // Request registration challenge from server
    const creationOptions = await this.getRegistrationOptions(userId, username);

    // Prompt user for biometric
    const credential = await startRegistration(creationOptions);

    // Verify and store credential on server
    const verifiedCredential = await this.verifyRegistration(
      userId,
      credential
    );

    return verifiedCredential;
  }

  /**
   * Authenticate using biometric credential
   */
  async authenticate(userId?: string): Promise<{
    verified: boolean;
    userId?: string;
    credentialId?: string;
  }> {
    // Request authentication challenge from server
    const requestOptions = await this.getAuthenticationOptions(userId);

    // Prompt user for biometric
    const assertion = await startAuthentication(requestOptions);

    // Verify assertion on server
    const result = await this.verifyAuthentication(assertion);

    return result;
  }

  /**
   * Get registration options from server
   */
  private async getRegistrationOptions(
    userId: string,
    username: string
  ): Promise<PublicKeyCredentialCreationOptions> {
    const response = await fetch('/api/auth/biometric/register/options', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId, username })
    });

    return await response.json();
  }

  /**
   * Verify registration on server
   */
  private async verifyRegistration(
    userId: string,
    credential: any
  ): Promise<BiometricCredential> {
    const response = await fetch('/api/auth/biometric/register/verify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId, credential })
    });

    const result = await response.json();

    if (!result.verified) {
      throw new Error('Biometric registration failed');
    }

    return result.credential;
  }

  /**
   * Get authentication options from server
   */
  private async getAuthenticationOptions(
    userId?: string
  ): Promise<PublicKeyCredentialRequestOptions> {
    const response = await fetch('/api/auth/biometric/login/options', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId })
    });

    return await response.json();
  }

  /**
   * Verify authentication on server
   */
  private async verifyAuthentication(assertion: any): Promise<{
    verified: boolean;
    userId?: string;
    credentialId?: string;
  }> {
    const response = await fetch('/api/auth/biometric/login/verify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ assertion })
    });

    const result = await response.json();

    return result;
  }

  /**
   * Detect authenticator type
   */
  private detectAuthenticatorType(): string {
    const userAgent = navigator.userAgent.toLowerCase();

    if (userAgent.includes('iphone') || userAgent.includes('ipad')) {
      return 'Face ID / Touch ID';
    } else if (userAgent.includes('android')) {
      return 'Fingerprint / Face Unlock';
    } else if (userAgent.includes('windows')) {
      return 'Windows Hello';
    } else if (userAgent.includes('mac')) {
      return 'Touch ID';
    }

    return 'Platform Authenticator';
  }
}

WebAuthn Passkeys: Passwordless Authentication

WebAuthn (Web Authentication) enables passwordless authentication using public-key cryptography. Instead of passwords, users authenticate with passkeys—cryptographic key pairs stored securely on their devices. During registration, the device generates a key pair; the public key is stored on the server while the private key never leaves the device. Authentication requires the device to sign a challenge using the private key, proving possession without transmitting the key.

Passkeys are phishing-resistant because they're bound to the origin (domain) and cannot be tricked into authenticating on fraudulent sites. They're also resistant to credential stuffing since there's no password to steal from database breaches. Modern passkeys sync across devices via cloud keychains (iCloud Keychain, Google Password Manager), providing both security and convenience.

Here's a production-ready WebAuthn passkey handler:

// webauthn-passkey-handler.ts
import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse
} from '@simplewebauthn/server';
import type {
  RegistrationResponseJSON,
  AuthenticationResponseJSON
} from '@simplewebauthn/typescript-types';

interface PasskeyCredential {
  credentialId: Buffer;
  publicKey: Buffer;
  counter: number;
  transports: AuthenticatorTransport[];
  backupEligible: boolean;
  backupState: boolean;
  uvInitialized: boolean;
}

export class WebAuthnPasskeyHandler {
  private readonly RP_NAME = 'MakeAIHQ ChatGPT App';
  private readonly RP_ID = 'makeaihq.com';
  private readonly ORIGIN = 'https://makeaihq.com';

  /**
   * Generate passkey registration options
   */
  async generateRegistrationOptions(
    userId: string,
    username: string,
    existingCredentials: PasskeyCredential[] = []
  ) {
    const options = await generateRegistrationOptions({
      rpName: this.RP_NAME,
      rpID: this.RP_ID,
      userID: userId,
      userName: username,
      userDisplayName: username,

      // Prevent re-registering existing credentials
      excludeCredentials: existingCredentials.map(cred => ({
        id: cred.credentialId,
        type: 'public-key',
        transports: cred.transports
      })),

      // Require resident keys (synced passkeys)
      authenticatorSelection: {
        residentKey: 'required',
        userVerification: 'required',
        authenticatorAttachment: 'platform'
      },

      // Support for backup/sync
      attestationType: 'none'
    });

    // Store challenge for verification
    await this.storeChallenge(userId, options.challenge);

    return options;
  }

  /**
   * Verify passkey registration response
   */
  async verifyRegistration(
    userId: string,
    response: RegistrationResponseJSON
  ): Promise<PasskeyCredential> {
    const expectedChallenge = await this.getChallenge(userId);

    if (!expectedChallenge) {
      throw new Error('Challenge not found or expired');
    }

    const verification = await verifyRegistrationResponse({
      response,
      expectedChallenge,
      expectedOrigin: this.ORIGIN,
      expectedRPID: this.RP_ID,
      requireUserVerification: true
    });

    if (!verification.verified || !verification.registrationInfo) {
      throw new Error('Passkey registration verification failed');
    }

    const {
      credentialPublicKey,
      credentialID,
      counter,
      credentialBackedUp,
      credentialDeviceType
    } = verification.registrationInfo;

    const credential: PasskeyCredential = {
      credentialId: Buffer.from(credentialID),
      publicKey: Buffer.from(credentialPublicKey),
      counter,
      transports: response.response.transports || [],
      backupEligible: credentialBackedUp,
      backupState: credentialBackedUp,
      uvInitialized: true
    };

    // Store credential in database
    await this.storeCredential(userId, credential);

    // Clear challenge
    await this.clearChallenge(userId);

    return credential;
  }

  /**
   * Generate passkey authentication options
   */
  async generateAuthenticationOptions(userId?: string) {
    const credentials = userId
      ? await this.getUserCredentials(userId)
      : [];

    const options = await generateAuthenticationOptions({
      rpID: this.RP_ID,
      userVerification: 'required',

      // If user is known, specify their credentials
      allowCredentials: credentials.map(cred => ({
        id: cred.credentialId,
        type: 'public-key',
        transports: cred.transports
      }))
    });

    // Store challenge
    const challengeKey = userId || 'anonymous';
    await this.storeChallenge(challengeKey, options.challenge);

    return options;
  }

  /**
   * Verify passkey authentication response
   */
  async verifyAuthentication(
    response: AuthenticationResponseJSON,
    userId?: string
  ): Promise<{ verified: boolean; userId?: string }> {
    // Find credential by ID
    const credential = await this.getCredentialById(
      Buffer.from(response.id, 'base64url')
    );

    if (!credential) {
      return { verified: false };
    }

    const expectedChallenge = await this.getChallenge(
      userId || 'anonymous'
    );

    if (!expectedChallenge) {
      throw new Error('Challenge not found or expired');
    }

    const verification = await verifyAuthenticationResponse({
      response,
      expectedChallenge,
      expectedOrigin: this.ORIGIN,
      expectedRPID: this.RP_ID,
      authenticator: {
        credentialPublicKey: credential.publicKey,
        credentialID: credential.credentialId,
        counter: credential.counter
      },
      requireUserVerification: true
    });

    if (!verification.verified) {
      return { verified: false };
    }

    // Update counter to prevent replay attacks
    await this.updateCredentialCounter(
      credential.credentialId,
      verification.authenticationInfo.newCounter
    );

    // Clear challenge
    await this.clearChallenge(userId || 'anonymous');

    return {
      verified: true,
      userId: credential.userId
    };
  }

  // Database operations (implement based on your DB)
  private async storeChallenge(key: string, challenge: string): Promise<void> {
    // Store in Redis with 5-minute expiration
  }

  private async getChallenge(key: string): Promise<string | null> {
    return null;
  }

  private async clearChallenge(key: string): Promise<void> {
    // Delete from Redis
  }

  private async storeCredential(
    userId: string,
    credential: PasskeyCredential
  ): Promise<void> {
    // Store in Firestore/MongoDB
  }

  private async getUserCredentials(
    userId: string
  ): Promise<PasskeyCredential[]> {
    return [];
  }

  private async getCredentialById(
    credentialId: Buffer
  ): Promise<PasskeyCredential & { userId: string } | null> {
    return null;
  }

  private async updateCredentialCounter(
    credentialId: Buffer,
    newCounter: number
  ): Promise<void> {
    // Update counter in database
  }
}

Device Trust: Fingerprinting and Trusted Devices

Device trust frameworks identify and track devices used to access your application. When users log in from new devices, you can require additional verification steps. When they log in from trusted devices, you can streamline authentication. Device fingerprinting generates unique identifiers by collecting browser characteristics, hardware information, and network attributes.

Modern device fingerprinting combines multiple signals: user agent, screen resolution, installed fonts, canvas rendering signatures, WebGL capabilities, audio context fingerprints, timezone, language preferences, and installed plugins. While no single attribute uniquely identifies a device, the combination creates a probabilistic fingerprint resistant to casual spoofing.

Here's a production-ready device fingerprinter:

// device-fingerprinter.ts
import { createHash } from 'crypto';

interface DeviceFingerprint {
  fingerprintId: string;
  components: {
    userAgent: string;
    language: string;
    timezone: string;
    screen: string;
    canvas: string;
    webgl: string;
    fonts: string[];
    plugins: string[];
  };
  confidence: number;
  createdAt: Date;
}

interface TrustedDevice {
  deviceId: string;
  userId: string;
  fingerprint: DeviceFingerprint;
  name: string;
  trusted: boolean;
  lastSeen: Date;
  firstSeen: Date;
}

export class DeviceFingerprinter {
  /**
   * Generate comprehensive device fingerprint
   */
  async generateFingerprint(): Promise<DeviceFingerprint> {
    const components = {
      userAgent: navigator.userAgent,
      language: navigator.language,
      timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
      screen: this.getScreenSignature(),
      canvas: await this.getCanvasSignature(),
      webgl: await this.getWebGLSignature(),
      fonts: await this.detectFonts(),
      plugins: this.getPlugins()
    };

    const fingerprintId = this.hashComponents(components);
    const confidence = this.calculateConfidence(components);

    return {
      fingerprintId,
      components,
      confidence,
      createdAt: new Date()
    };
  }

  /**
   * Get screen signature
   */
  private getScreenSignature(): string {
    const { width, height, colorDepth, pixelDepth } = window.screen;
    return `${width}x${height}x${colorDepth}x${pixelDepth}`;
  }

  /**
   * Get canvas fingerprint
   */
  private async getCanvasSignature(): Promise<string> {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    if (!ctx) {
      return 'unavailable';
    }

    // Draw unique pattern
    ctx.textBaseline = 'top';
    ctx.font = '14px Arial';
    ctx.fillStyle = '#f60';
    ctx.fillRect(125, 1, 62, 20);
    ctx.fillStyle = '#069';
    ctx.fillText('MakeAIHQ Canvas', 2, 15);

    // Get image data hash
    const dataURL = canvas.toDataURL();
    return this.hash(dataURL);
  }

  /**
   * Get WebGL signature
   */
  private async getWebGLSignature(): Promise<string> {
    const canvas = document.createElement('canvas');
    const gl = canvas.getContext('webgl') ||
               canvas.getContext('experimental-webgl');

    if (!gl) {
      return 'unavailable';
    }

    const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');

    if (!debugInfo) {
      return 'unavailable';
    }

    const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
    const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);

    return `${vendor}|${renderer}`;
  }

  /**
   * Detect installed fonts
   */
  private async detectFonts(): Promise<string[]> {
    const baseFonts = ['monospace', 'sans-serif', 'serif'];
    const testFonts = [
      'Arial', 'Verdana', 'Times New Roman', 'Courier New',
      'Georgia', 'Palatino', 'Garamond', 'Bookman',
      'Comic Sans MS', 'Trebuchet MS', 'Impact'
    ];

    const detectedFonts: string[] = [];
    const testString = 'mmmmmmmmmmlli';
    const testSize = '72px';

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d')!;

    // Measure base fonts
    const baseMeasurements = baseFonts.map(font => {
      ctx.font = `${testSize} ${font}`;
      return ctx.measureText(testString).width;
    });

    // Test each font
    for (const font of testFonts) {
      for (let i = 0; i < baseFonts.length; i++) {
        ctx.font = `${testSize} ${font}, ${baseFonts[i]}`;
        const width = ctx.measureText(testString).width;

        if (width !== baseMeasurements[i]) {
          detectedFonts.push(font);
          break;
        }
      }
    }

    return detectedFonts;
  }

  /**
   * Get installed plugins
   */
  private getPlugins(): string[] {
    if (!navigator.plugins) {
      return [];
    }

    return Array.from(navigator.plugins)
      .map(plugin => plugin.name)
      .sort();
  }

  /**
   * Hash fingerprint components
   */
  private hashComponents(components: any): string {
    const componentString = JSON.stringify(components, null, 0);
    return this.hash(componentString);
  }

  /**
   * Calculate fingerprint confidence score
   */
  private calculateConfidence(components: any): number {
    let score = 0;

    if (components.canvas !== 'unavailable') score += 30;
    if (components.webgl !== 'unavailable') score += 25;
    if (components.fonts.length > 5) score += 20;
    if (components.plugins.length > 0) score += 10;
    if (components.screen) score += 10;
    if (components.timezone) score += 5;

    return Math.min(score, 100);
  }

  /**
   * Hash string using SHA-256
   */
  private hash(data: string): string {
    return createHash('sha256').update(data).digest('hex');
  }

  /**
   * Check if device is trusted
   */
  async isTrustedDevice(
    userId: string,
    fingerprint: DeviceFingerprint
  ): Promise<boolean> {
    const trustedDevices = await this.getTrustedDevices(userId);

    return trustedDevices.some(device =>
      device.fingerprint.fingerprintId === fingerprint.fingerprintId
    );
  }

  // Database operations
  private async getTrustedDevices(userId: string): Promise<TrustedDevice[]> {
    // Retrieve from database
    return [];
  }
}

Session Management: Tokens and Timeout Policies

Session management controls how long users remain authenticated and detects suspicious session activity. Secure session management implements absolute timeouts (maximum session duration regardless of activity), idle timeouts (inactivity limits), and concurrent session controls (maximum simultaneous sessions per user). Session tokens should be cryptographically random, stored securely, and rotated regularly.

Modern session management also implements device binding—associating sessions with specific device fingerprints to prevent session hijacking. If a session token is used from a different device or location than where it was issued, additional verification is required. Security-sensitive actions (password changes, MFA modifications) should require session re-authentication regardless of session age.

Here's a production-ready session manager:

// session-manager.ts
import { randomBytes, createHash } from 'crypto';

interface Session {
  sessionId: string;
  userId: string;
  deviceFingerprint: string;
  ipAddress: string;
  userAgent: string;
  createdAt: Date;
  lastActivity: Date;
  expiresAt: Date;
  requiresReauth?: boolean;
}

interface SessionConfig {
  absoluteTimeout: number;      // 7 days
  idleTimeout: number;           // 30 minutes
  maxConcurrentSessions: number; // 5
  requireReauthForSensitive: boolean;
}

export class SessionManager {
  private readonly config: SessionConfig = {
    absoluteTimeout: 7 * 24 * 60 * 60 * 1000,  // 7 days
    idleTimeout: 30 * 60 * 1000,                // 30 minutes
    maxConcurrentSessions: 5,
    requireReauthForSensitive: true
  };

  /**
   * Create new session
   */
  async createSession(
    userId: string,
    deviceFingerprint: string,
    ipAddress: string,
    userAgent: string
  ): Promise<string> {
    // Enforce max concurrent sessions
    await this.enforceMaxSessions(userId);

    const sessionId = this.generateSessionId();
    const now = new Date();

    const session: Session = {
      sessionId,
      userId,
      deviceFingerprint,
      ipAddress,
      userAgent,
      createdAt: now,
      lastActivity: now,
      expiresAt: new Date(now.getTime() + this.config.absoluteTimeout)
    };

    await this.storeSession(session);

    return sessionId;
  }

  /**
   * Validate session
   */
  async validateSession(
    sessionId: string,
    deviceFingerprint: string,
    ipAddress: string
  ): Promise<{ valid: boolean; userId?: string; requiresReauth?: boolean }> {
    const session = await this.getSession(sessionId);

    if (!session) {
      return { valid: false };
    }

    const now = new Date();

    // Check absolute timeout
    if (now > session.expiresAt) {
      await this.destroySession(sessionId);
      return { valid: false };
    }

    // Check idle timeout
    const idleTime = now.getTime() - session.lastActivity.getTime();
    if (idleTime > this.config.idleTimeout) {
      await this.destroySession(sessionId);
      return { valid: false };
    }

    // Check device binding
    if (session.deviceFingerprint !== deviceFingerprint) {
      // Device mismatch - require re-authentication
      session.requiresReauth = true;
      await this.updateSession(session);

      return {
        valid: true,
        userId: session.userId,
        requiresReauth: true
      };
    }

    // Update last activity
    session.lastActivity = now;
    await this.updateSession(session);

    return {
      valid: true,
      userId: session.userId,
      requiresReauth: session.requiresReauth
    };
  }

  /**
   * Destroy session (logout)
   */
  async destroySession(sessionId: string): Promise<void> {
    await this.deleteSession(sessionId);
  }

  /**
   * Enforce maximum concurrent sessions
   */
  private async enforceMaxSessions(userId: string): Promise<void> {
    const sessions = await this.getUserSessions(userId);

    if (sessions.length >= this.config.maxConcurrentSessions) {
      // Remove oldest session
      sessions.sort((a, b) =>
        a.lastActivity.getTime() - b.lastActivity.getTime()
      );

      await this.destroySession(sessions[0].sessionId);
    }
  }

  /**
   * Generate cryptographically secure session ID
   */
  private generateSessionId(): string {
    return randomBytes(32).toString('hex');
  }

  // Database operations
  private async storeSession(session: Session): Promise<void> {
    // Store in Redis/Firestore
  }

  private async getSession(sessionId: string): Promise<Session | null> {
    return null;
  }

  private async updateSession(session: Session): Promise<void> {
    // Update in database
  }

  private async deleteSession(sessionId: string): Promise<void> {
    // Delete from database
  }

  private async getUserSessions(userId: string): Promise<Session[]> {
    return [];
  }
}

Authentication Security Logging and Anomaly Detection

Comprehensive authentication logging provides visibility into authentication events and enables anomaly detection. Security logs should capture successful authentications, failed attempts, MFA enrollments, device trust changes, and session lifecycle events. Each log entry should include contextual metadata: timestamp, user ID, IP address, device fingerprint, authentication method, and outcome.

Anomaly detection identifies suspicious authentication patterns: impossible travel (authentication from geographically distant locations within short timeframes), brute force attempts (repeated failed logins), credential stuffing (authentication attempts using leaked credentials), and session hijacking (session usage from unexpected devices or locations). Automated responses can include rate limiting, temporary account locks, MFA step-up challenges, or security team notifications.

Here's a production-ready authentication logger with anomaly detection:

// authentication-logger.ts
interface AuthenticationEvent {
  eventId: string;
  userId?: string;
  email?: string;
  eventType:
    | 'login_success'
    | 'login_failure'
    | 'mfa_enrollment'
    | 'mfa_verification'
    | 'passkey_registration'
    | 'passkey_authentication'
    | 'session_created'
    | 'session_destroyed'
    | 'device_trusted';
  method: 'password' | 'totp' | 'biometric' | 'passkey';
  deviceFingerprint: string;
  ipAddress: string;
  userAgent: string;
  location?: {
    country: string;
    city: string;
    coordinates: { lat: number; lon: number };
  };
  success: boolean;
  failureReason?: string;
  timestamp: Date;
  metadata?: Record<string, any>;
}

interface AnomalyDetection {
  anomalyType:
    | 'impossible_travel'
    | 'brute_force'
    | 'credential_stuffing'
    | 'new_device'
    | 'new_location'
    | 'session_hijacking';
  severity: 'low' | 'medium' | 'high' | 'critical';
  confidence: number;
  description: string;
  recommendedAction: string;
}

export class AuthenticationLogger {
  /**
   * Log authentication event
   */
  async logEvent(event: Omit<AuthenticationEvent, 'eventId' | 'timestamp'>): Promise<void> {
    const fullEvent: AuthenticationEvent = {
      eventId: this.generateEventId(),
      timestamp: new Date(),
      ...event
    };

    // Store event in security log
    await this.storeEvent(fullEvent);

    // Check for anomalies
    if (event.userId) {
      const anomalies = await this.detectAnomalies(event.userId, fullEvent);

      for (const anomaly of anomalies) {
        await this.handleAnomaly(event.userId, anomaly, fullEvent);
      }
    }
  }

  /**
   * Detect authentication anomalies
   */
  private async detectAnomalies(
    userId: string,
    currentEvent: AuthenticationEvent
  ): Promise<AnomalyDetection[]> {
    const anomalies: AnomalyDetection[] = [];
    const recentEvents = await this.getRecentEvents(userId, 24 * 60 * 60 * 1000); // 24 hours

    // Impossible travel detection
    const travelAnomaly = this.detectImpossibleTravel(currentEvent, recentEvents);
    if (travelAnomaly) anomalies.push(travelAnomaly);

    // Brute force detection
    const bruteForceAnomaly = this.detectBruteForce(recentEvents);
    if (bruteForceAnomaly) anomalies.push(bruteForceAnomaly);

    // New device detection
    const newDeviceAnomaly = await this.detectNewDevice(userId, currentEvent);
    if (newDeviceAnomaly) anomalies.push(newDeviceAnomaly);

    return anomalies;
  }

  /**
   * Detect impossible travel (authentication from distant locations)
   */
  private detectImpossibleTravel(
    currentEvent: AuthenticationEvent,
    recentEvents: AuthenticationEvent[]
  ): AnomalyDetection | null {
    if (!currentEvent.location) return null;

    for (const event of recentEvents) {
      if (!event.location || !event.success) continue;

      const distance = this.calculateDistance(
        currentEvent.location.coordinates,
        event.location.coordinates
      );

      const timeDiff = currentEvent.timestamp.getTime() - event.timestamp.getTime();
      const hoursDiff = timeDiff / (1000 * 60 * 60);

      // If distance > 500km and time < 1 hour, flag as impossible travel
      if (distance > 500 && hoursDiff < 1) {
        return {
          anomalyType: 'impossible_travel',
          severity: 'high',
          confidence: 0.9,
          description: `Authentication from ${currentEvent.location.city} (${distance.toFixed(0)}km from previous location) within ${hoursDiff.toFixed(1)} hours`,
          recommendedAction: 'Require MFA step-up or block authentication'
        };
      }
    }

    return null;
  }

  /**
   * Detect brute force attacks
   */
  private detectBruteForce(events: AuthenticationEvent[]): AnomalyDetection | null {
    const failures = events.filter(e =>
      e.eventType === 'login_failure' && !e.success
    );

    if (failures.length >= 5) {
      return {
        anomalyType: 'brute_force',
        severity: failures.length >= 10 ? 'critical' : 'high',
        confidence: 0.95,
        description: `${failures.length} failed login attempts in 24 hours`,
        recommendedAction: 'Temporarily lock account and notify user'
      };
    }

    return null;
  }

  /**
   * Detect new device
   */
  private async detectNewDevice(
    userId: string,
    currentEvent: AuthenticationEvent
  ): Promise<AnomalyDetection | null> {
    const knownDevices = await this.getKnownDevices(userId);

    const isKnown = knownDevices.some(device =>
      device.fingerprint === currentEvent.deviceFingerprint
    );

    if (!isKnown) {
      return {
        anomalyType: 'new_device',
        severity: 'medium',
        confidence: 0.8,
        description: `Authentication from new device: ${currentEvent.userAgent}`,
        recommendedAction: 'Require email verification or MFA'
      };
    }

    return null;
  }

  /**
   * Handle detected anomaly
   */
  private async handleAnomaly(
    userId: string,
    anomaly: AnomalyDetection,
    event: AuthenticationEvent
  ): Promise<void> {
    // Log anomaly
    await this.storeAnomaly(userId, anomaly, event);

    // Take action based on severity
    switch (anomaly.severity) {
      case 'critical':
        await this.lockAccount(userId);
        await this.notifySecurityTeam(userId, anomaly);
        break;

      case 'high':
        await this.requireMFAStepUp(userId);
        await this.notifyUser(userId, anomaly);
        break;

      case 'medium':
        await this.notifyUser(userId, anomaly);
        break;
    }
  }

  /**
   * Calculate distance between coordinates (Haversine formula)
   */
  private calculateDistance(
    coord1: { lat: number; lon: number },
    coord2: { lat: number; lon: number }
  ): number {
    const R = 6371; // Earth radius in km
    const dLat = this.toRadians(coord2.lat - coord1.lat);
    const dLon = this.toRadians(coord2.lon - coord1.lon);

    const a =
      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(this.toRadians(coord1.lat)) *
      Math.cos(this.toRadians(coord2.lat)) *
      Math.sin(dLon / 2) * Math.sin(dLon / 2);

    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
  }

  private toRadians(degrees: number): number {
    return degrees * (Math.PI / 180);
  }

  private generateEventId(): string {
    return randomBytes(16).toString('hex');
  }

  // Database operations
  private async storeEvent(event: AuthenticationEvent): Promise<void> {
    // Store in security log database
  }

  private async getRecentEvents(
    userId: string,
    timeWindow: number
  ): Promise<AuthenticationEvent[]> {
    return [];
  }

  private async getKnownDevices(
    userId: string
  ): Promise<Array<{ fingerprint: string }>> {
    return [];
  }

  private async storeAnomaly(
    userId: string,
    anomaly: AnomalyDetection,
    event: AuthenticationEvent
  ): Promise<void> {
    // Store anomaly for security review
  }

  private async lockAccount(userId: string): Promise<void> {
    // Temporarily lock account
  }

  private async requireMFAStepUp(userId: string): Promise<void> {
    // Flag session to require MFA re-verification
  }

  private async notifySecurityTeam(
    userId: string,
    anomaly: AnomalyDetection
  ): Promise<void> {
    // Send alert to security team
  }

  private async notifyUser(
    userId: string,
    anomaly: AnomalyDetection
  ): Promise<void> {
    // Send email/SMS notification to user
  }
}

Conclusion: Building Defense-in-Depth Authentication

Authentication security requires layered defenses—no single control provides complete protection. Multi-factor authentication prevents credential-based attacks even when passwords are compromised. Biometric authentication combines security with user experience. WebAuthn passkeys eliminate password vulnerabilities entirely while enabling seamless cross-device synchronization. Device fingerprinting establishes trust relationships and detects suspicious access patterns. Session management controls authentication lifecycle and prevents session hijacking. Comprehensive logging and anomaly detection provide visibility and enable automated threat response.

Implement these controls incrementally, starting with TOTP MFA for immediate security improvement, then adding biometric authentication and passkeys as your user base grows. Deploy device fingerprinting to reduce authentication friction for trusted devices while requiring additional verification for new devices. Establish robust session management policies that balance security with user experience. Build comprehensive authentication logging from day one—security incidents are easier to investigate and remediate when you have complete visibility into authentication events.

Ready to secure your ChatGPT app? Start building on MakeAIHQ and implement enterprise-grade authentication in minutes, not months. Our platform includes built-in MFA support, WebAuthn integration, and security logging—so you can focus on building features instead of security infrastructure.


Related Resources

Pillar Pages

  • Security & Compliance for ChatGPT Apps: Complete Guide
  • ChatGPT App Builder Platform: Complete Technical Guide

Related Cluster Articles

  • OAuth 2.1 Implementation Guide for ChatGPT Apps
  • Secrets Management Best Practices for ChatGPT Apps
  • Security Incident Response for ChatGPT Applications
  • GDPR Compliance for ChatGPT Apps: Data Privacy Guide
  • SOC 2 Type II Certification for ChatGPT Apps
  • Rate Limiting and DDoS Protection
  • Input Validation and Sanitization Guide

External Resources


About MakeAIHQ: We're the leading no-code platform for building ChatGPT applications. From zero to ChatGPT App Store in 48 hours—no coding required. Start your free trial today.