JSPM

@chrisfh/secure-otp

1.0.0
    • ESM via JSPM
    • ES Module Entrypoint
    • Export Map
    • Keywords
    • License
    • Repository URL
    • TypeScript Types
    • README
    • Created
    • Published
    • Downloads 102
    • Score
      100M100P100Q20938F
    • License MIT

    Modern 2025-compliant MFA/OTP system using TOTP (RFC 6238) with advanced security features

    Package Exports

    • @chrisfh/secure-otp

    Readme

    @chrisfh/secure-otp

    A modern, 2025-compliant MFA/OTP library implementing TOTP (RFC 6238) with advanced security features.

    License: MIT Node Version

    ๐Ÿ” Features

    Core Security (2025 Standards)

    • โœ… Modern Cryptography: HMAC-SHA512 and HMAC-SHA256 (no SHA1)
    • โœ… Strong Entropy: Minimum 160-bit (20-byte) secrets
    • โœ… Encrypted Storage: AES-256-GCM encryption for secrets using WebCrypto API
    • โœ… Timing Attack Protection: Constant-time comparison using crypto.timingSafeEqual
    • โœ… Rate Limiting: Built-in protection against brute-force attacks

    TOTP Features

    • โœ… 6-digit and 8-digit code support
    • โœ… 30-second time step (configurable)
    • โœ… ยฑ1 time-step drift tolerance (configurable)
    • โœ… RFC 6238 compliant

    Developer Experience

    • โœ… Full TypeScript support with type definitions
    • โœ… ESM-only (modern Node.js)
    • โœ… Comprehensive test coverage
    • โœ… QR code generation for easy provisioning
    • โœ… Compatible with Google Authenticator, Authy, and other TOTP apps

    ๐Ÿ“ฆ Installation

    npm install @chrisfh/secure-otp

    Requirements:

    • Node.js >= 20.0.0 (for WebCrypto API support)

    ๐Ÿš€ Quick Start

    import {
      generateSecret,
      generateTOTP,
      verifyTOTP,
      generateQRCodeForSecret
    } from '@chrisfh/secure-otp';
    
    // 1. Generate a secret for a new user
    const secret = generateSecret();
    
    // 2. Generate a QR code for user to scan
    const { url, qrCode } = await generateQRCodeForSecret(secret, {
      account: 'user@example.com',
      issuer: 'MyApp'
    });
    
    // Display the QR code to user (qrCode is a Data URL)
    console.log('Scan this QR code:', qrCode);
    
    // 3. Verify user's OTP code during login
    const userCode = '123456'; // Code from user's authenticator app
    const isValid = await verifyTOTP(userCode, secret);
    
    if (isValid) {
      console.log('โœ“ Authentication successful!');
    } else {
      console.log('โœ— Invalid code');
    }

    ๐Ÿ“– API Documentation

    Secret Generation

    generateSecret(options?)

    Generate a cryptographically secure random secret.

    import { generateSecret } from '@chrisfh/secure-otp';
    
    // Default: 20 bytes (160 bits)
    const secret = generateSecret();
    
    // Custom length (minimum 20 bytes enforced)
    const longSecret = generateSecret({ length: 32 });

    Options:

    • length?: number - Length in bytes (default: 20, minimum: 20)

    Returns: string - Base32-encoded secret


    TOTP Generation

    generateTOTP(secret, options?)

    Generate a TOTP code for the current time.

    import { generateTOTP } from '@chrisfh/secure-otp';
    
    // Generate 6-digit code with SHA-512
    const code = await generateTOTP(secret);
    
    // Generate 8-digit code with SHA-256
    const code8 = await generateTOTP(secret, {
      digits: 8,
      algorithm: 'SHA-256'
    });

    Options:

    • period?: number - Time step in seconds (default: 30)
    • digits?: 6 | 8 - Number of digits (default: 6)
    • algorithm?: 'SHA-256' | 'SHA-512' - HMAC algorithm (default: 'SHA-512')

    Returns: Promise<string> - Zero-padded OTP code


    TOTP Verification

    verifyTOTP(token, secret, options?)

    Verify a TOTP code with time drift tolerance.

    import { verifyTOTP } from '@chrisfh/secure-otp';
    
    const userCode = '123456';
    const isValid = await verifyTOTP(userCode, secret, {
      window: 1, // Allow ยฑ1 time step (ยฑ30 seconds)
      algorithm: 'SHA-512'
    });

    Options:

    • period?: number - Time step in seconds (default: 30)
    • digits?: 6 | 8 - Number of digits (default: 6)
    • algorithm?: 'SHA-256' | 'SHA-512' - HMAC algorithm (default: 'SHA-512')
    • window?: number - Drift tolerance in time steps (default: 1)

    Returns: Promise<boolean> - true if valid, false otherwise


    QR Code Generation

    generateQRCodeForSecret(secret, options)

    Generate an otpauth:// URL and QR code for easy provisioning.

    import { generateQRCodeForSecret } from '@chrisfh/secure-otp';
    
    const { url, qrCode } = await generateQRCodeForSecret(secret, {
      account: 'user@example.com',
      issuer: 'MyApp',
      digits: 6,
      algorithm: 'SHA512'
    });
    
    // Display QR code to user
    console.log('OTPAuth URL:', url);
    console.log('QR Code Data URL:', qrCode);

    Options:

    • account: string - User's email or username (required)
    • issuer: string - App or company name (required)
    • period?: number - Time step in seconds (default: 30)
    • digits?: 6 | 8 - Number of digits (default: 6)
    • algorithm?: 'SHA256' | 'SHA512' - Algorithm name (default: 'SHA512')

    Returns: Promise<{ url: string, qrCode: string }> - URL and Data URL for QR code


    Secure Storage

    encryptSecret(secret, options)

    Encrypt a secret using AES-256-GCM.

    import { encryptSecret, generateMasterKey } from '@chrisfh/secure-otp';
    
    const masterKey = generateMasterKey();
    const encrypted = await encryptSecret(secret, { masterKey });
    
    // Store encrypted.ciphertext, encrypted.iv, and encrypted.authTag in database

    Options:

    • masterKey: Buffer - 32-byte encryption key (required)

    Returns: Promise<EncryptedSecret> - Object with ciphertext, iv, and authTag

    decryptSecret(encrypted, options)

    Decrypt a previously encrypted secret.

    import { decryptSecret } from '@chrisfh/secure-otp';
    
    const decrypted = await decryptSecret(encrypted, { masterKey });
    // Use decrypted secret for TOTP operations

    Options:

    • masterKey: Buffer - Same 32-byte key used for encryption (required)

    Returns: Promise<string> - Decrypted Base32-encoded secret

    generateMasterKey()

    Generate a secure 32-byte master key for encryption.

    import { generateMasterKey } from '@chrisfh/secure-otp';
    
    const masterKey = generateMasterKey();
    // Store this securely (e.g., environment variable, key management service)

    Returns: Buffer - 32-byte master key


    Rate Limiting

    RateLimiter

    Prevent brute-force attacks with built-in rate limiting.

    import { RateLimiter, createDefaultRateLimiter } from '@chrisfh/secure-otp';
    
    // Default: 5 attempts per minute
    const rateLimiter = createDefaultRateLimiter();
    
    // Or custom configuration
    const customLimiter = new RateLimiter({
      maxAttempts: 3,
      windowMs: 60000 // 1 minute
    });
    
    // Check if request is allowed
    const userId = 'user@example.com';
    if (rateLimiter.check(userId)) {
      // Verify OTP
      const isValid = await verifyTOTP(code, secret);
      if (isValid) {
        rateLimiter.reset(userId); // Reset on success
      }
    } else {
      console.log('Rate limit exceeded');
    }

    Methods:

    • check(identifier: string): boolean - Check if request is allowed
    • reset(identifier: string): void - Reset rate limit for an identifier
    • getRemainingAttempts(identifier: string): number - Get remaining attempts
    • cleanup(): void - Remove expired rate limit states

    ๐Ÿ”’ Security Best Practices

    1. Secret Storage

    Never store secrets in plain text! Always encrypt them:

    import { generateSecret, encryptSecret, generateMasterKey } from '@chrisfh/secure-otp';
    
    // Generate and encrypt
    const secret = generateSecret({ length: 32 }); // 256 bits
    const masterKey = generateMasterKey(); // Store in environment variable
    const encrypted = await encryptSecret(secret, { masterKey });
    
    // Store encrypted.ciphertext, encrypted.iv, encrypted.authTag in database

    2. Rate Limiting

    Always implement rate limiting to prevent brute-force attacks:

    import { createDefaultRateLimiter, verifyTOTP } from '@chrisfh/secure-otp';
    
    const rateLimiter = createDefaultRateLimiter();
    
    app.post('/verify-otp', async (req, res) => {
      const { userId, code } = req.body;
      
      if (!rateLimiter.check(userId)) {
        return res.status(429).json({ error: 'Too many attempts' });
      }
      
      const isValid = await verifyTOTP(code, secret);
      
      if (isValid) {
        rateLimiter.reset(userId);
        return res.json({ success: true });
      } else {
        return res.status(401).json({ error: 'Invalid code' });
      }
    });

    3. Use Strong Algorithms

    Always use SHA-256 or SHA-512 (never SHA-1):

    // โœ“ Good
    await generateTOTP(secret, { algorithm: 'SHA-512' });
    
    // โœ— Bad (not supported)
    // await generateTOTP(secret, { algorithm: 'SHA-1' });

    4. Time Drift Tolerance

    Use a reasonable window (default is ยฑ1 time step = ยฑ30 seconds):

    // โœ“ Balanced security and usability
    await verifyTOTP(code, secret, { window: 1 });
    
    // โš  Less secure but more tolerant
    await verifyTOTP(code, secret, { window: 2 });
    
    // โš  More secure but less tolerant
    await verifyTOTP(code, secret, { window: 0 });

    ๐Ÿงช Testing

    Run the test suite:

    npm test

    Run tests with coverage:

    npm run test -- --coverage

    ๐Ÿ“ Examples

    See the examples/demo.js file for complete usage examples including:

    • New user setup with QR code
    • Login verification with rate limiting
    • Secure secret encryption/decryption
    • 8-digit codes with SHA-256
    • Enterprise scenarios

    Run the demo:

    npm run build
    node examples/demo.js

    ๐Ÿ—๏ธ Architecture

    This library uses:

    • Node.js WebCrypto API (crypto.subtle) for HMAC and AES-GCM
    • Base32 encoding (RFC 4648) for secret representation
    • TOTP algorithm (RFC 6238) with modern hash functions
    • Constant-time comparison to prevent timing attacks

    ๐Ÿ“„ License

    MIT License - see LICENSE file for details.


    ๐Ÿค Contributing

    Contributions are welcome! Please ensure:

    1. All tests pass
    2. Code coverage remains high
    3. TypeScript types are properly defined
    4. Security best practices are followed

    ๐Ÿ”— Resources


    โš ๏ธ Changelog

    1.0.0 (2025)

    • Initial release
    • TOTP with SHA-256 and SHA-512
    • AES-256-GCM encryption
    • QR code generation
    • Rate limiting
    • Full TypeScript support
    • Comprehensive test coverage

    Built with โค๏ธ for security in 2025