Multi-Factor Authentication

Add TOTP, SMS, Email, and WebAuthn authentication factors.

Overview

Multi-factor authentication (MFA) adds an extra layer of security by requiring users to verify their identity with something they have (phone, security key) in addition to their password.

Supported Factors

FactorDescriptionSecurity Level
TOTPTime-based one-time passwords (Authenticator apps)High
SMSText message codesMedium
EmailEmail verification codesMedium
WebAuthnBiometrics and security keysHighest

MFA Policies

Configure when MFA is required.

Policy Levels

LevelDescription
DISABLEDMFA is not available
OPTIONALUsers can choose to enable MFA
REQUIREDAll users must set up MFA
RISK_BASEDMFA triggered by risk signals

Configure Policy

TypeScript:

await client.auth.mfa.updatePolicy({
  requirement: 'OPTIONAL',
  allowedFactors: ['TOTP', 'WEBAUTHN'],
  recoveryCodesCount: 10,
  gracePeriod: {
    value: 7,
    unit: 'DAYS',
  },
  trustedDevices: {
    enabled: true,
    durationDays: 30,
  },
});

Python:

client.auth.mfa.update_policy(
    requirement="OPTIONAL",
    allowed_factors=["TOTP", "WEBAUTHN"],
    recovery_codes_count=10,
    grace_period={
        "value": 7,
        "unit": "DAYS",
    },
)

Risk-Based MFA

Trigger MFA based on risk signals:

await client.auth.mfa.updatePolicy({
  requirement: 'RISK_BASED',
  triggers: {
    newDevice: true,
    newLocation: true,
    suspiciousIp: true,
    afterPasswordChange: true,
  },
});

TOTP (Authenticator Apps)

Enroll TOTP

// Start enrollment
const enrollment = await client.auth.mfa.enrollTotp('user_xxx');
 
// Show QR code to user
console.log('Secret:', enrollment.secret);
console.log('QR URI:', enrollment.qrCodeUri);
// Generate QR code from qrCodeUri

Verify TOTP

// User enters code from authenticator app
const factor = await client.auth.mfa.verifyTotp('user_xxx', {
  code: '123456',
});
 
console.log('TOTP enrolled:', factor.id);

TOTP Integration Example

// Frontend: Display QR code
const QRCode = require('qrcode');
 
async function setupTotp(userId: string) {
  // Get enrollment data
  const enrollment = await client.auth.mfa.enrollTotp(userId);
 
  // Generate QR code image
  const qrImage = await QRCode.toDataURL(enrollment.qrCodeUri);
 
  return {
    qrImage,
    secret: enrollment.secret, // For manual entry
  };
}
 
// User submits code
async function verifyTotp(userId: string, code: string) {
  try {
    await client.auth.mfa.verifyTotp(userId, { code });
    return { success: true };
  } catch (error) {
    return { success: false, error: 'Invalid code' };
  }
}

SMS MFA

Enroll SMS

// Register phone number
await client.auth.mfa.enrollSms('user_xxx', {
  phoneNumber: '+14155551234',
});
 
// Send verification code
await client.auth.mfa.sendSmsCode('user_xxx');

Verify SMS

const factor = await client.auth.mfa.verifySms('user_xxx', {
  code: '123456',
});

Email MFA

Enroll Email

// Use user's verified email
await client.auth.mfa.enrollEmail('user_xxx');
 
// Send verification code
await client.auth.mfa.sendEmailCode('user_xxx');

Verify Email

const factor = await client.auth.mfa.verifyEmail('user_xxx', {
  code: '123456',
});

WebAuthn (Biometrics & Security Keys)

Supported Authenticators

  • Touch ID / Face ID (macOS, iOS)
  • Windows Hello
  • Android biometrics
  • Hardware security keys (YubiKey, etc.)

Register WebAuthn

// Get registration options
const options = await client.auth.mfa.getWebAuthnRegistrationOptions('user_xxx');
 
// Browser creates credential
const credential = await navigator.credentials.create({
  publicKey: options,
});
 
// Complete registration
const factor = await client.auth.mfa.verifyWebAuthn('user_xxx', {
  credential: credential,
  name: 'MacBook Touch ID',
});

Authenticate with WebAuthn

// Get authentication options
const options = await client.auth.mfa.getWebAuthnAuthOptions('user_xxx');
 
// Browser authenticates
const assertion = await navigator.credentials.get({
  publicKey: options,
});
 
// Verify assertion
await client.auth.mfa.verifyWebAuthnAssertion('user_xxx', {
  assertion: assertion,
});

Managing Factors

List User Factors

const factors = await client.auth.mfa.listFactors('user_xxx');
 
for (const factor of factors) {
  console.log(`${factor.type}: ${factor.name || 'Unnamed'}`);
  console.log(`  Enrolled: ${factor.createdAt}`);
  console.log(`  Last used: ${factor.lastUsedAt}`);
}

Remove a Factor

await client.auth.mfa.removeFactor('user_xxx', 'factor_xxx');

Set Primary Factor

await client.auth.mfa.setPrimary('user_xxx', 'factor_xxx');

Recovery Codes

Backup codes for when users lose access to their MFA device.

Generate Recovery Codes

const codes = await client.auth.mfa.generateRecoveryCodes('user_xxx');
 
// Display codes to user (only shown once!)
for (const code of codes) {
  console.log(code); // e.g., "XXXX-XXXX-XXXX"
}

Use Recovery Code

await client.auth.mfa.useRecoveryCode('user_xxx', {
  code: 'XXXX-XXXX-XXXX',
});

Regenerate Recovery Codes

// Invalidates old codes
const newCodes = await client.auth.mfa.regenerateRecoveryCodes('user_xxx');

MFA During Login

Challenge Flow

// Step 1: Primary authentication
const result = await authenticate(email, password);
 
if (result.mfaRequired) {
  // Step 2: MFA challenge
  const challenge = result.challenge;
 
  // Show available factors
  for (const factor of challenge.factors) {
    console.log(`${factor.type}: ${factor.id}`);
  }
 
  // User selects factor and enters code
  const verified = await client.auth.mfa.verify({
    challengeId: challenge.id,
    factorId: selectedFactorId,
    code: userCode,
  });
 
  if (verified) {
    // Issue tokens
    return verified.tokens;
  }
}

Remember Device

Skip MFA on trusted devices:

const verified = await client.auth.mfa.verify({
  challengeId: challenge.id,
  factorId: factorId,
  code: code,
  rememberDevice: true, // Trust this device
});
 
// Set cookie with device token
res.cookie('mfa_device', verified.deviceToken, {
  maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
  httpOnly: true,
  secure: true,
});

Admin Operations

Require MFA Reset

// Force user to re-enroll MFA
await client.auth.mfa.resetFactors('user_xxx');

View MFA Status

const status = await client.auth.mfa.getStatus('user_xxx');
 
console.log('MFA enabled:', status.enabled);
console.log('Factors:', status.factorCount);
console.log('Recovery codes remaining:', status.recoveryCodesRemaining);

Best Practices

1. Encourage Strong Factors

  • Prioritize WebAuthn and TOTP
  • Consider deprecating SMS for high-security apps
  • Require MFA for admin accounts

2. Provide Recovery Options

  • Always generate recovery codes
  • Allow multiple factors
  • Support factor recovery workflows

3. User Experience

  • Remember trusted devices
  • Use risk-based MFA to reduce friction
  • Provide clear setup instructions

4. Security

  • Implement rate limiting on code verification
  • Log MFA events for audit
  • Alert on unusual MFA activity

Next Steps

  • SSO - Enterprise single sign-on
  • Security - Security policies
  • Webhooks - MFA event notifications