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

> Implement TOTP two-factor authentication with Instatic. Learn about AES-256 encryption constant-time verification and hashed recovery codes in this technical guide.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: deep-dive
- Published: 2026-08-02

---

**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`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/mfa.ts) and [`server/auth/totpSecrets.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/mfa.ts) to the verification flows in [`server/handlers/cms/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/server/db/migrations-pg.ts) (lines 91-93) and [`server/db/migrations-sqlite.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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:

```typescript
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:

```typescript
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:

```typescript
// 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:

```typescript
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`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/mfa.ts) and [`server/auth/totpSecrets.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/totpSecrets.ts) (lines 68-78) and `verifyTotpCode` in [`server/auth/mfa.ts`](https://github.com/CoreBunch/Instatic/blob/main/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.