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 addressloginRateLimit: 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:
- The decrypted TOTP secret via
verifyEncryptedTotpCode(which delegates toverifyTotpCodeinserver/auth/mfa.ts, lines 28-35) - 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/sessionsreturns metadata for all active sessions viahandleListSessions - Revoke single session:
DELETE /admin/api/cms/auth/sessions/:idinvalidates specific sessions - Global logout:
POST /admin/api/cms/logout-allrevokes all sessions except the current one viahandleLogoutAll - Standard logout:
POST /logoutclears 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.tsand verification viaserver/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →