Instatic Auth System TOTP Two-Factor Authentication Implementation: Complete Technical Guide

Instatic implements RFC 6238 compliant TOTP two-factor authentication using AES-256 encrypted secrets at rest, constant-time verification algorithms, and hashed recovery codes, with the core cryptographic functions residing in server/auth/mfa.ts and server/auth/totpSecrets.ts.

Instatic, the open-source content management platform maintained by CoreBunch, provides production-grade multi-factor authentication through a Time-Based One-Time Password (TOTP) system. The implementation prioritizes security by encrypting secrets before database persistence and supporting fallback recovery codes. This article examines the complete technical architecture, from secret generation in server/auth/mfa.ts to the verification flows in server/handlers/cms/auth.ts.

TOTP Secret Generation and Provisioning

The enrollment process begins with secret generation handled by the generateTotpSecret function in server/auth/mfa.ts (lines 8-26). This function creates a cryptographically secure random base-32 string that serves as the shared secret between the server and the user's authenticator app.

For provisioning, the system generates an otpauth URI via totpProvisioningUri, which encodes the secret, issuer name, and account identifier into a standardized format. This URI is typically rendered as a QR code that users scan with applications like Google Authenticator or Authy. The provisioning logic ensures the secret is displayed to the user exactly once during the enrollment window before being encrypted for storage.

Encryption at Rest Architecture

Instatic never stores TOTP secrets in plaintext. Instead, the platform implements a three-column encryption strategy that separates the ciphertext from the initialization vector and key fingerprint.

Database Schema

The database migrations in server/db/migrations-pg.ts (lines 91-93) and server/db/migrations-sqlite.ts (lines 87-89) add the following columns to the users table:

  • mfa_totp_secret_ciphertext – The AES-256 encrypted secret (bytea in PostgreSQL, blob in SQLite)
  • mfa_totp_secret_iv – The initialization vector used for encryption
  • mfa_totp_secret_key_fingerprint – A hash identifying the encryption key version for rotation support

Encryption API

The server/auth/totpSecrets.ts file provides the encryptTotpSecret function (lines 35-43), which encrypts the plain TOTP secret using the server-wide master key. During verification, the verifyEncryptedTotpCode function (lines 68-78) retrieves these components, validates the key fingerprint for consistency, and decrypts the secret before validation.

Error handling follows a secure pattern through totpSecretErrorResponse (lines 81-91), ensuring that decryption failures do not leak sensitive timing information that could aid attackers.

Verification Flow

Login-time Verification

When a user with enabled MFA attempts login, the authentication handler in server/handlers/cms/auth.ts (lines 451-455) invokes verifyUserTotpCode, which delegates to verifyEncryptedTotpCode. This routine:

  1. Retrieves the encrypted secret components from the database
  2. Validates the key fingerprint against the current master key
  3. Decrypts the secret using the stored IV
  4. Executes the TOTP algorithm via verifyTotpCode in server/auth/mfa.ts (lines 28-35)

If the submitted code matches the computed TOTP for the current time window, the session is marked as MFA-verified and authentication proceeds. Otherwise, the system returns a 401 Unauthorized response without distinguishing between decryption failures and invalid codes.

Constant-Time Comparison

The verification implementation utilizes constant-time comparison algorithms to prevent timing attacks that could reveal information about the secret or valid codes through microsecond-level timing differences.

Recovery Codes Implementation

To mitigate the risk of authenticator app loss, Instatic generates single-use recovery codes through generateRecoveryCodes in server/auth/mfa.ts (lines 38-58). These codes:

  • Are generated as high-entropy strings (e.g., a1b2-c3d4-e5f6)
  • Are hashed using hashRecoveryCode before storage (never stored plaintext)
  • Are validated using findMatchingRecoveryCodeHash with constant-time comparison
  • Become invalid immediately after use

When a user consumes a recovery code, the system removes the hash from the database and grants access, while optionally triggering an email notification to alert the account owner.

Public API Endpoints

The MFA management interface exposes RESTful endpoints under /admin/api/cms/me/mfa/totp, implemented in server/handlers/cms/me.ts (lines 178-241):

Method Path Purpose
POST /start Generates a new secret and returns the provisioning URI for QR code generation
POST /enable Validates a user-submitted TOTP code against the secret, then encrypts and persists the secret
DELETE / Removes the encrypted secret from the user record, disabling MFA
GET / Validates a supplied TOTP code (primarily used during the login flow)

These endpoints enforce authentication and CSRF protection, ensuring only the legitimate account owner can modify MFA settings.

Practical Implementation Examples

Generating a QR Code for Enrollment

When initiating MFA setup, the client requests a new secret and renders it as a scannable QR code:

import { apiRequest } from '@core/http';
import { QRCode } from 'react-qr-svg';

async function enrollMfa() {
  const { secret, otpauthUrl } = await apiRequest('/admin/api/cms/me/mfa/totp/start', {
    method: 'POST',
    schema: z.object({ secret: z.string(), otpauthUrl: z.string() })
  });
  
  // Display QR code for authenticator app scanning
  return <QRCode value={otpauthUrl} size={200} />;
}

Enabling TOTP After Verification

After the user scans the QR code and enters the current 6-digit code:

async function enableMfa(secret: string, code: string) {
  await apiRequest('/admin/api/cms/me/mfa/totp/enable', {
    method: 'POST',
    body: { secret, code },
    schema: z.object({}) // Empty success response
  });
  // MFA is now active; recovery codes should be generated next
}

Server-Side Verification During Login

The login handler decrypts and verifies the TOTP code before issuing a session:

// Simplified excerpt from server/handlers/cms/auth.ts
const totpResult = await verifyEncryptedTotpCode(
  user.encryptedMfaTotpSecret, 
  submittedCode
);

if (!totpResult) {
  return jsonResponse({ error: 'Invalid MFA code' }, { status: 401 });
}
// Proceed with authenticated session creation

Generating and Storing Recovery Codes

When creating backup codes for users:

import { generateRecoveryCodes, hashRecoveryCode } from '@core/mfa';

// Generate plaintext codes for display
const rawCodes = generateRecoveryCodes(); // ['a1b2-c3d4-e5f6', ...]

// Hash before database storage
const hashedCodes = rawCodes.map(hashRecoveryCode);
// Store hashedCodes; display rawCodes once to the user

Summary

  • Instatic implements standard RFC 6238 TOTP through modular functions in server/auth/mfa.ts and server/auth/totpSecrets.ts.
  • Secrets are encrypted at rest using AES-256 with separate storage for ciphertext, IV, and key fingerprints across PostgreSQL or SQLite backends.
  • Verification occurs through verifyEncryptedTotpCode during login, utilizing constant-time comparisons to prevent timing attacks.
  • Recovery codes provide fallback access, stored as hashes generated by hashRecoveryCode in server/auth/mfa.ts.
  • Management endpoints at /admin/api/cms/me/mfa/totp handle enrollment, enabling, disabling, and verification through handlers in server/handlers/cms/me.ts.

Frequently Asked Questions

How does Instatic encrypt TOTP secrets?

Instatic encrypts TOTP secrets using AES-256 via the encryptTotpSecret function in server/auth/totpSecrets.ts (lines 35-43). The encrypted data is split across three database columns: mfa_totp_secret_ciphertext, mfa_totp_secret_iv, and mfa_totp_secret_key_fingerprint. This separation allows for key rotation and ensures the secret is never stored in plaintext or reversible without the server-wide master key.

What happens if a user loses access to their authenticator device?

Users can regain access through recovery codes generated by generateRecoveryCodes in server/auth/mfa.ts (lines 38-58). These single-use codes are hashed with hashRecoveryCode before storage and validated using constant-time comparison. Once a recovery code is used, it is immediately invalidated, and the user should regenerate a new set through the account security settings.

Which API endpoints manage TOTP enrollment?

The MFA enrollment flow utilizes four endpoints under /admin/api/cms/me/mfa/totp defined in server/handlers/cms/me.ts (lines 178-241). The POST /start endpoint initiates enrollment by generating a secret, POST /enable confirms the user's code and activates MFA, DELETE / removes the secret to disable MFA, and GET / validates codes during authentication challenges.

How does the system prevent timing attacks during verification?

The verification chain uses constant-time comparison operations through verifyEncryptedTotpCode in server/auth/totpSecrets.ts (lines 68-78) and verifyTotpCode in server/auth/mfa.ts (lines 28-35). Additionally, totpSecretErrorResponse (lines 81-91) normalizes error handling to ensure that decryption failures and invalid code submissions return identical timing profiles, preventing attackers from distinguishing between different failure modes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →