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 (byteain PostgreSQL,blobin SQLite)mfa_totp_secret_iv– The initialization vector used for encryptionmfa_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:
- Retrieves the encrypted secret components from the database
- Validates the key fingerprint against the current master key
- Decrypts the secret using the stored IV
- Executes the TOTP algorithm via
verifyTotpCodeinserver/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
hashRecoveryCodebefore storage (never stored plaintext) - Are validated using
findMatchingRecoveryCodeHashwith 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.tsandserver/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
verifyEncryptedTotpCodeduring login, utilizing constant-time comparisons to prevent timing attacks. - Recovery codes provide fallback access, stored as hashes generated by
hashRecoveryCodeinserver/auth/mfa.ts. - Management endpoints at
/admin/api/cms/me/mfa/totphandle enrollment, enabling, disabling, and verification through handlers inserver/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →