# Multi-factor authentication available

- **Pattern:** `ab-002403` (`security-hardening.input-validation.mfa-available`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002403
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002403)

## Why it matters

Without MFA, a stolen or phished password is sufficient to compromise any account. CWE-308 (Use of Single-Factor Authentication) and OWASP A07 (Identification & Authentication Failures) both flag password-only auth as a systemic weakness. NIST 800-53 IA-2(1) mandates multi-factor authentication for privileged accounts and for any system handling sensitive data. Credential phishing is the most common initial access vector in reported breaches; MFA stops the majority of these attacks even after credentials are compromised. Admin accounts without MFA are an especially high-value target because a single compromise grants full application access.

## Severity rationale

Medium because MFA absence doesn't directly expose a vulnerability, but it means a stolen password alone — the most commonly leaked credential type — provides full account access.

## Remediation

Implement TOTP-based MFA using `otplib`. Store the secret encrypted; only mark `totpEnabled: true` after the user successfully verifies their first code:

```ts
import { authenticator } from 'otplib'

// Enrollment: generate and return QR code
const secret = authenticator.generateSecret()
const otpauth = authenticator.keyuri(user.email, 'YourApp', secret)
// Save encrypt(secret) to database with totpEnabled: false

// Verification: check code before enabling
const isValid = authenticator.check(userCode, secret)
if (isValid) await db.user.update({ where: { id }, data: { totpEnabled: true } })
```

Force MFA enrollment for admin roles on first login. Provide backup codes at enrollment time and store them hashed.

## Detection

- **ID:** `mfa-available`
- **Severity:** `medium`
- **What to look for:** Count all authentication flows (login, registration, account recovery). For each, check whether the application supports any form of multi-factor authentication: TOTP (authenticator apps), SMS codes, email codes, WebAuthn/passkeys, or hardware keys. Look for `otpauth`, `speakeasy`, `otplib` dependencies (TOTP), WebAuthn libraries, or managed auth provider MFA settings.
- **Pass criteria:** MFA is available for user accounts, and is enforced for privileged/admin accounts. The MFA implementation includes proper backup codes or recovery mechanisms — at least 1 MFA method (TOTP, SMS, or WebAuthn) available for all user accounts. Report: "X auth flows found, MFA available on Y."
- **Fail criteria:** No MFA option is available to users. Or MFA exists but is not enforced for admin accounts.
- **Skip (N/A) when:** The application is an internal tool with a small, trusted user base and MFA is enforced at the SSO/identity provider level before reaching the app. Or the project is an early prototype not yet handling sensitive data.
- **Detail on fail:** `"No MFA implementation found — admin accounts protected only by password"` or `"TOTP support exists for regular users but admin roles have no MFA enforcement"`
- **Remediation:** For TOTP-based MFA using `otplib`:

  ```ts
  import { authenticator } from 'otplib'
  import QRCode from 'qrcode'

  // Enrollment
  const secret = authenticator.generateSecret()
  const otpauth = authenticator.keyuri(user.email, 'YourApp', secret)
  const qrDataUrl = await QRCode.toDataURL(otpauth)

  // Store secret encrypted in database
  await db.user.update({
    where: { id: user.id },
    data: { totpSecret: encrypt(secret), totpEnabled: false },  // not enabled until verified
  })

  // Verification (after user scans QR and enters first code)
  const isValid = authenticator.check(token, secret)
  if (isValid) {
    await db.user.update({ where: { id: user.id }, data: { totpEnabled: true } })
  }
  ```

  Require MFA during login for accounts with totpEnabled: true, and force it during first privileged action for admin accounts.

## External references

- cwe CWE-308
- owasp A07
- nist IA-2(1)

Taxons: access-control

HTML version: https://auditbuffet.com/patterns/ab-002403
