# Instatic Authentication Flow with TOTP and Account Lockout: Security Implementation Guide

> Learn the Instatic authentication flow combining TOTP MFA, account lockout, and IP rate limiting. Secure your CMS with this guide.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: security-implementation-guide
- Published: 2026-07-26

---

**Instatic implements a three-layered defense combining per-IP rate limiting, exponential back-off account lockout, and optional TOTP-based MFA, orchestrated by [`server/handlers/cms/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/auth.ts) with constant-time password verification and encrypted secret storage.**

Instatic is a CMS that protects administrative interfaces through a hardened authentication pipeline. This article examines how the `CoreBunch/Instatic` repository implements time-based one-time password (TOTP) multi-factor authentication alongside intelligent account lockout mechanisms to prevent brute-force attacks and credential stuffing.

## Three-Layered Defense Architecture

The authentication system relies on three specialized modules working in concert:

- **[`server/auth/lockout.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/lockout.ts)** – Calculates exponential back-off windows via `evaluateFailedAttempt` and checks lock status via `evaluateLockState`.
- **[`server/auth/mfa.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/mfa.ts)** – Generates secrets, builds provisioning URIs, and validates codes through `verifyTotpCode`.
- **[`server/auth/totpSecrets.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/totpSecrets.ts)** – Encrypts and decrypts TOTP secrets using the server master key, exposing `verifyEncryptedTotpCode` for safe verification.

These modules support the main auth handler in [`server/handlers/cms/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/auth.ts), which coordinates rate limiting, credential verification, and session management.

## Initial Login and Rate Limiting

The flow begins at `POST /admin/api/cms/login` in [`server/handlers/cms/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/auth.ts). The endpoint applies two rate-limiting layers before credential verification:

- **IP-wide limiting** – `loginPerIpRateLimit.consume(ip)` tracks all login attempts from a single IP address.
- **IP + email tuple limiting** – `loginRateLimit.consume(rateLimitKey)` tracks attempts targeting specific credentials.

If either limiter triggers, the server responds with HTTP **429** and a **Retry-After** header indicating the cooldown period.

To prevent timing attacks, `verifyPassword` executes in constant time regardless of whether the user exists in the database. This ensures attackers cannot enumerate valid email addresses through response timing analysis.

## Account Lockout Policy

After password verification, the system checks the user's `locked_until` column via `evaluateLockState` in [`server/auth/lockout.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/lockout.ts) (lines 95-115). If the account is locked, the server returns HTTP **423** (Locked) with a **Retry-After** header specifying the remaining lockout duration.

The lockout policy implements exponential back-off:

- **Threshold** – `LOCKOUT_THRESHOLD = 5` consecutive failures.
- **Initial duration** – `LOCKOUT_INITIAL_MS = 15` minutes.
- **Escalation** – Each subsequent lockout doubles the duration.
- **Maximum** – `LOCKOUT_CAP_MS = 24` hours.

When a failure occurs, `evaluateFailedAttempt(prevCount)` returns whether to trigger a lockout, the new `lockedUntil` timestamp, and the updated failure count. This function is called after both failed password attempts and failed MFA attempts, ensuring brute-force resistance across both authentication factors.

## TOTP MFA Verification

If the user has MFA enabled, the login response returns `{ mfaRequired: true }` and creates a pending session with `mfaPassedAt: null`. The client must then submit the TOTP code to `POST /admin/api/cms/auth/mfa/verify`.

The MFA endpoint enforces additional protections:

1. **Account lockout check** – `evaluateLockState` runs again to prevent session cookie brute-forcing (lines 19-25).
2. **MFA rate limiting** – `mfaRateLimit.consume(rateLimitKey)` applies a third layer of rate limiting specific to code verification attempts.

The verification process in [`server/auth/totpSecrets.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/totpSecrets.ts) decrypts the stored secret using `decryptTotpSecret`, then validates the code through `verifyTotpCode` in [`server/auth/mfa.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/mfa.ts) (lines 68-76).

### Recovery Codes and Failure Handling

If TOTP verification fails, the system checks for a valid recovery code via `findMatchingRecoveryCodeHash`. These one-time codes provide account recovery when TOTP devices are unavailable.

Failed MFA attempts trigger the same lockout logic as password failures. The handler calls `evaluateFailedAttempt` to potentially extend the lockout window (lines 63-70), preventing attackers from bypassing lockouts by switching from password attacks to MFA brute-forcing.

### Session Rotation

Upon successful MFA verification, the server calls `rotateSessionToken` to issue a fresh session token, sets the `mfaPassedAt` timestamp, and invalidates the pending session. This prevents session fixation attacks by ensuring the post-MFA session differs from the pre-MFA pending session. Each decision point logs events via `createAuditEvent` for forensic visibility.

## Step-Up Re-Authentication

Sensitive operations (such as user deletion) require elevated privileges obtained through `POST /admin/api/cms/auth/step-up`. This endpoint mirrors the login flow:

- Applies IP and account rate limiting.
- Verifies the password via constant-time comparison.
- Checks account lockout status via `evaluateLockState`.
- Optionally verifies MFA through `verifyStepUpMfa`.

Upon successful step-up authentication, the server issues a new session token with a `stepUpExpiresAt` timestamp controlled by `stepUpWindowMs`. This short-lived elevated session restricts the time window for privileged actions.

## Complete Authentication Examples

The following examples demonstrate client-side interaction with the authentication API:

### Initial Login Request

```typescript
await fetch('/admin/api/cms/login', {
  method: 'POST',
  body: JSON.stringify({ email, password }),
  headers: { 'Content-Type': 'application/json' },
})
  .then(r => r.json())
  .then(data => {
    if (data.mfaRequired) {
      // Display TOTP input dialog, then submit to /auth/mfa/verify
    } else {
      // Authentication complete; session cookie set automatically
    }
  });

```

### MFA Verification

```typescript
await fetch('/admin/api/cms/auth/mfa/verify', {
  method: 'POST',
  body: JSON.stringify({ code: totpCode }),
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include', // Required to send the pending session cookie
});

```

### Step-Up Authentication

```typescript
await fetch('/admin/api/cms/auth/step-up', {
  method: 'POST',
  body: JSON.stringify({ password, mfaCode: totpCode }),
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
});

```

### Server-Side Lockout Implementation

```typescript
import { evaluateFailedAttempt, LOCKOUT_THRESHOLD } from '@/server/auth/lockout';

const lockout = evaluateFailedAttempt(user.failedLoginCount);
await recordFailedLoginAttempt(db, user.id, lockout.lockedUntil);

if (lockout.triggered) {
  // Return HTTP 423 with Retry-After header
}

```

## Summary

- **Three-layered defense** combines IP rate limiting, account lockout with exponential back-off (15 minutes to 24 hours), and optional TOTP MFA.
- **Constant-time password verification** prevents user enumeration through timing analysis.
- **Encrypted TOTP storage** in [`server/auth/totpSecrets.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/totpSecrets.ts) protects secrets at rest using the server master key.
- **Unified lockout policy** applies to both password and MFA failures, preventing bypass attempts.
- **Session rotation** after MFA verification prevents fixation attacks.
- **Step-up authentication** provides short-lived elevated sessions for sensitive operations.

## Frequently Asked Questions

### How does Instatic prevent brute-force attacks against the login endpoint?

Instatic implements **dual rate limiting** through `loginPerIpRateLimit` and `loginRateLimit`, restricting both IP-wide traffic and per-credential guessing. Additionally, the **account lockout** system in [`server/auth/lockout.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/lockout.ts) tracks consecutive failures and enforces exponential back-off, starting at 15 minutes and capping at 24 hours.

### What happens if a user loses their TOTP device?

The system supports **recovery codes** via `findMatchingRecoveryCodeHash`. During MFA verification, if `verifyTotpCode` fails, the server checks the submitted code against stored recovery code hashes. Valid recovery codes bypass TOTP requirements but can only be used once.

### Why does the MFA verification endpoint check account lockout status again?

The MFA endpoint applies `evaluateLockState` to prevent **session brute-forcing**. If an attacker steals a pending session cookie (created after correct password entry), they could attempt to guess the TOTP code. The secondary lockout check ensures these attempts still trigger account lockouts and rate limiting.

### How does step-up authentication differ from standard login?

Step-up authentication (`POST /admin/api/cms/auth/step-up`) mirrors the login flow but issues a **short-lived elevated session** with a `stepUpExpiresAt` timestamp. Unlike the standard session, this token grants elevated privileges only for the duration specified in `stepUpWindowMs`, requiring re-authentication for subsequent sensitive operations.