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
| Factor | Description | Security Level |
|---|---|---|
| TOTP | Time-based one-time passwords (Authenticator apps) | High |
| SMS | Text message codes | Medium |
| Email verification codes | Medium | |
| WebAuthn | Biometrics and security keys | Highest |
MFA Policies
Configure when MFA is required.
Policy Levels
| Level | Description |
|---|---|
DISABLED | MFA is not available |
OPTIONAL | Users can choose to enable MFA |
REQUIRED | All users must set up MFA |
RISK_BASED | MFA 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 qrCodeUriVerify 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
On This Page
- Overview
- Supported Factors
- MFA Policies
- Policy Levels
- Configure Policy
- Risk-Based MFA
- TOTP (Authenticator Apps)
- Enroll TOTP
- Verify TOTP
- TOTP Integration Example
- SMS MFA
- Enroll SMS
- Verify SMS
- Email MFA
- Enroll Email
- Verify Email
- WebAuthn (Biometrics & Security Keys)
- Supported Authenticators
- Register WebAuthn
- Authenticate with WebAuthn
- Managing Factors
- List User Factors
- Remove a Factor
- Set Primary Factor
- Recovery Codes
- Generate Recovery Codes
- Use Recovery Code
- Regenerate Recovery Codes
- MFA During Login
- Challenge Flow
- Remember Device
- Admin Operations
- Require MFA Reset
- View MFA Status
- Best Practices
- 1. Encourage Strong Factors
- 2. Provide Recovery Options
- 3. User Experience
- 4. Security
- Next Steps