Authentication and MFA Security Architecture in Instatic: A Deep Dive

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 (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:

  • 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 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 (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, 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 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 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 (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 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 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 and 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, while server/auth/authz.ts provides middleware to extract session hashes and enforce authentication boundaries.

Implementation Examples

Authenticating with Password Only

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

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

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

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.
  • TOTP-based MFA: Implements encrypted secret storage in server/auth/totpSecrets.ts and verification via 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.
  • 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 implements sliding-window tracking, while 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, 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 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, 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, 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.

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 →