# Authentication and MFA Security Architecture in Instatic: A Deep Dive

> Explore Instatic's robust authentication and MFA security architecture. Learn how stateless cookies, TOTP, step-up re authentication, and advanced rate limiting protect your accounts.

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

---

**Instatic implements a hardened authentication stack using stateless session cookies, time-based one-time passwords (TOTP) for MFA, step-up re-authentication, and layered rate-limiting with exponential back-off account lockouts.**

The CoreBunch/Instatic repository provides a production-ready CMS authentication system built entirely on the server side using Bun. The architecture prioritizes security through constant-time password checks, encrypted TOTP secrets, and defensive programming patterns that mitigate credential stuffing and brute-force attacks.

## Primary Login Flow and Session Initialization

The entry point for authentication is the `handleLogin` function in [`server/handlers/cms/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/auth.ts) (lines 67-104). This handler implements a defense-in-depth strategy that proceeds through multiple validation layers before establishing a session.

**Rate Limiting Gates**
Every login attempt first passes through two in-memory rate limiters exported from [`server/auth/rateLimit.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/rateLimit.ts):
- **`loginPerIpRateLimit`**: Caps at 30 attempts per 10-minute window per IP address
- **`loginRateLimit`**: Restricts to 5 attempts per 15-minute window per IP-email tuple

**Constant-Time Password Verification**
To prevent timing-based user enumeration, the system verifies passwords using Argon2id in constant time. Even when an email does not exist in the database, the server processes a dummy hash comparison to ensure identical response times for existing and non-existing accounts.

**Account Lockout Evaluation**
Before credential validation, `evaluateLockState` from [`server/auth/lockout.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/lockout.ts) checks the `users.locked_until` column. Locked accounts receive a 423 response with a `Retry-After` header indicating the remaining lockout duration.

**Session Creation Logic**
Upon successful verification, the handler inserts a new row into the `sessions` table. If the user has `mfaEnabled: true`, the session is created with `mfaPassedAt: null` and the API returns `{ mfaRequired: true }`. Otherwise, `mfaPassedAt` is set to the current timestamp and the client receives a fully authenticated session cookie immediately.

## Multi-Factor Authentication Implementation

When MFA is enabled, users must complete a second verification step via `POST /admin/api/cms/auth/mfa/verify`, handled by `handleMfaVerify` in [`server/handlers/cms/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/auth.ts) (lines 11-51).

**TOTP Verification Process**
The endpoint extracts the pending session token using `getSessionHash` and retrieves the user via `findUserByPendingMfaSessionHash`. The submitted code undergoes verification against:
1. The decrypted TOTP secret via `verifyEncryptedTotpCode` (which delegates to `verifyTotpCode` in [`server/auth/mfa.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/mfa.ts), lines 28-35)
2. The stored SHA-256 hashed recovery codes via `findMatchingRecoveryCodeHash`

**MFA Rate Limiting**
A dedicated `mfaRateLimit` instance allows 10 attempts per 10-minute window per IP address. Failed attempts trigger `evaluateFailedAttempt` from the lockout module and emit audit events for security monitoring.

**Recovery Code Consumption**
Recovery codes are generated as 10 random 12-character strings, hashed with SHA-256, and stored in the database. The `consumeUserRecoveryCodeHash` function in [`server/auth/mfa.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/mfa.ts) validates and immediately invalidates used codes, providing a secure fallback when authenticator devices are unavailable.

### Encrypted Secret Storage

TOTP secrets are never stored in plaintext. The [`server/auth/totpSecrets.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/totpSecrets.ts) module encrypts secrets using the server's master key loaded from `loadMasterKey`. The encryption routine returns a ciphertext bundle including `{ ciphertext, iv, keyFingerprint }`.

Decryption validates the stored fingerprint against the current master key. A fingerprint mismatch—indicating a rotated master key—surfaces as a `TotpSecretError` (HTTP 409), forcing the user to re-enroll their MFA device.

## Step-Up Re-Authentication Architecture

Sensitive administrative actions trigger step-up authentication via `handleStepUp` in [`server/handlers/cms/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/auth.ts) (lines 27-71). This mechanism requires users to re-prove their identity before executing high-risk operations like deleting users or revoking all sessions.

**Verification Flow**
The endpoint accepts the current password and optionally a TOTP code (if MFA is enabled) via `verifyStepUpMfa`. Upon successful validation, the system sets a `stepUpExpiresAt` timestamp in the session row, creating a temporary privilege window. Subsequent requests to protected endpoints check this timestamp before allowing execution.

## Rate Limiting and Account Lockout Defense

The `RateLimiter` class in [`server/auth/rateLimit.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/rateLimit.ts) implements a sliding-window log algorithm using an in-memory Map. Three exported instances protect the authentication surface:

| Limiter | Threshold | Window |
|---------|-----------|--------|
| `loginRateLimit` | 5 attempts | 15 minutes |
| `loginPerIpRateLimit` | 30 attempts | 10 minutes |
| `mfaRateLimit` | 10 attempts | 10 minutes |

**Exponential Back-Off Lockout**
The [`server/auth/lockout.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/lockout.ts) module enforces escalating penalties after `LOCKOUT_THRESHOLD` (5) consecutive failed password attempts:
- **Initial lockout**: 15 minutes (`LOCKOUT_INITIAL_MS`)
- **Escalation**: Duration doubles with each subsequent lockout
- **Maximum cap**: 24 hours (`LOCKOUT_CAP_MS`)

Successful authentication clears both the failed attempt counter and the `locked_until` timestamp via `markUserLoggedIn`.

## Session Management and Token Rotation

Session lifecycle management is centralized in [`server/handlers/cms/authSessions.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/authSessions.ts) and [`server/auth/sessions.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/sessions.ts).

**Core Operations**
- **List sessions**: `GET /admin/api/cms/auth/sessions` returns metadata for all active sessions via `handleListSessions`
- **Revoke single session**: `DELETE /admin/api/cms/auth/sessions/:id` invalidates specific sessions
- **Global logout**: `POST /admin/api/cms/logout-all` revokes all sessions except the current one via `handleLogoutAll`
- **Standard logout**: `POST /logout` clears the client cookie and deletes the database row

Session tokens are generated, hashed, and signed using utilities in [`server/auth/tokens.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/tokens.ts), while [`server/auth/authz.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/authz.ts) provides middleware to extract session hashes and enforce authentication boundaries.

## Implementation Examples

### Authenticating with Password Only

```typescript
const response = await fetch('/admin/api/cms/login', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, password }),
});

const data = await response.json();
if (data.mfaRequired) {
  // Redirect to TOTP input screen
}

```

### Verifying TOTP or Recovery Code

```typescript
const response = await fetch('/admin/api/cms/auth/mfa/verify', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ code: userSuppliedCode }),
});

if (response.ok) {
  // Session cookie now contains fully-authenticated session
}

```

### Step-Up Authentication for Sensitive Actions

```typescript
const response = await fetch('/admin/api/cms/auth/step-up', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    password: currentPassword,
    mfaCode: totpCode // Required if user has MFA enabled
  }),
});

const { stepUpExpiresAt } = await response.json();
// Proceed with privileged operation within the time window

```

### Listing Active Sessions

```typescript
const response = await fetch('/admin/api/cms/auth/sessions', {
  credentials: 'include',
});
const { sessions } = await response.json();
// Returns array of session metadata including creation time and user agent

```

## Summary

- **Stateless session architecture**: Uses signed cookies with server-side session rows in PostgreSQL, managed through [`server/handlers/cms/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/auth.ts).
- **TOTP-based MFA**: Implements encrypted secret storage in [`server/auth/totpSecrets.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/totpSecrets.ts) and verification via [`server/auth/mfa.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/mfa.ts), with SHA-256 hashed recovery codes for backup access.
- **Step-up authentication**: Requires re-verification for sensitive operations through `handleStepUp`, creating time-bounded privilege windows.
- **Layered rate limiting**: Three separate sliding-window limiters protect against brute force (IP-based, IP-email tuple, and MFA-specific).
- **Exponential lockout**: Account lockouts start at 15 minutes and double up to 24 hours after 5 failed attempts, implemented in [`server/auth/lockout.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/lockout.ts).
- **Constant-time checks**: Prevents user enumeration through dummy Argon2id hash comparisons for non-existent emails.

## Frequently Asked Questions

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

Instatic employs a three-tier defense: per-IP rate limiting (30 attempts per 10 minutes), per-credential rate limiting (5 attempts per 15 minutes per IP-email tuple), and account-specific exponential lockouts starting after 5 failed attempts. The `RateLimiter` class in [`server/auth/rateLimit.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/rateLimit.ts) implements sliding-window tracking, while [`server/auth/lockout.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/lockout.ts) enforces escalating delays up to 24 hours.

### What happens if the server master key rotates while users have MFA enabled?

When decrypting TOTP secrets in [`server/auth/totpSecrets.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/totpSecrets.ts), the system compares the stored `keyFingerprint` against the current master key fingerprint. A mismatch triggers a `TotpSecretError` (HTTP 409), forcing the user to re-enroll their authenticator device. This ensures old encrypted secrets cannot be decrypted with new keys, maintaining cryptographic hygiene.

### Can recovery codes be used instead of TOTP codes during MFA verification?

Yes. The `handleMfaVerify` function in [`server/handlers/cms/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/auth.ts) checks submitted codes against both the decrypted TOTP secret and the stored SHA-256 hashes of recovery codes. Valid recovery codes are consumed immediately via `consumeUserRecoveryCodeHash` in [`server/auth/mfa.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/mfa.ts), preventing reuse. Each user receives 10 single-use recovery codes during MFA enrollment.

### How does step-up authentication differ from standard MFA verification?

Standard MFA verification completes the initial login flow for sessions where `mfaPassedAt` is null. Step-up authentication, handled by `handleStepUp` in [`server/handlers/cms/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/auth.ts), occurs within an already authenticated session when users attempt sensitive actions. It requires re-entering the password and optionally a TOTP code, then sets a temporary `stepUpExpiresAt` window rather than modifying the persistent MFA state.