TOTP Standard
Works with any authenticator app
Backup Codes
Recovery codes for lost devices
Rate Limited
Brute-force protection built-in
Enforceable
Require 2FA for specific roles
Overview
Two-factor authentication (2FA) adds an extra layer of security by requiring users to provide a time-based one-time password (TOTP) in addition to their regular password. Users can use authenticator apps like Google Authenticator, Authy, or 1Password.
Enable 2FA Flow
Generate Secret
Call twoFactor.setup() to generate a secret and QR code URL.
Scan QR Code
User scans the QR code with their authenticator app (or enters secret manually).
Verify Code
User enters a 6-digit code from their app to verify setup.
Save Backup Codes
Display backup codes and remind user to save them securely.
'use client'
import { useAuth } from '@sylphx/platform-sdk/react'
import { useState } from 'react'
import QRCode from 'qrcode.react' // or any QR code library
export function Enable2FA() {
const { twoFactor } = useAuth()
const [step, setStep] = useState<'init' | 'verify' | 'backup' | 'done'>('init')
const [setupData, setSetupData] = useState<{ secret: string; qrCodeUrl: string } | null>(null)
const [verifyCode, setVerifyCode] = useState('')
const [backupCodes, setBackupCodes] = useState<string[]>([])
// Step 1: Generate secret and QR code
const startSetup = async () => {
const data = await twoFactor.setup()
setSetupData(data)
setStep('verify')
}
// Step 2: Verify the code works
const verifySetup = async () => {
const result = await twoFactor.verify(verifyCode)
if (result.success) {
setBackupCodes(result.backupCodes)
setStep('backup')
}
}
if (step === 'init') {
return (
<div>
<h2>Enable Two-Factor Authentication</h2>
<p>Add an extra layer of security to your account.</p>
<button onClick={startSetup}>Get Started</button>
</div>
)
}
if (step === 'verify' && setupData) {
return (
<div>
<h2>Scan QR Code</h2>
<QRCode value={setupData.qrCodeUrl} size={200} />
<p>Or enter manually: <code>{setupData.secret}</code></p>
<input
type="text"
placeholder="Enter 6-digit code"
value={verifyCode}
onChange={e => setVerifyCode(e.target.value)}
maxLength={6}
/>
<button onClick={verifySetup}>Verify & Enable</button>
</div>
)
}
if (step === 'backup') {
return (
<div>
<h2>Save Backup Codes</h2>
<p>Save these in a safe place:</p>
<div className="grid grid-cols-2 gap-2">
{backupCodes.map((code, i) => (
<code key={i}>{code}</code>
))}
</div>
<button onClick={() => setStep('done')}>I've Saved My Codes</button>
</div>
)
}
return <div className="text-green-600">ā 2FA is now enabled!</div>
}Sign In with 2FA
When a user with 2FA enabled signs in, they'll need to provide a code:
'use client'
import { useAuth } from '@sylphx/platform-sdk/react'
import { useState } from 'react'
export function LoginForm() {
const { signIn } = useAuth()
const [step, setStep] = useState<'credentials' | '2fa'>('credentials')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [twoFactorCode, setTwoFactorCode] = useState('')
const [challengeToken, setChallengeToken] = useState('')
const handleCredentials = async (e: React.FormEvent) => {
e.preventDefault()
try {
await signIn({ email, password })
// Success - user doesn't have 2FA
} catch (error) {
// Check if 2FA is required
const err = error as { code?: string; challengeToken?: string }
if (err.code === 'TWO_FACTOR_REQUIRED' && err.challengeToken) {
setChallengeToken(err.challengeToken)
setStep('2fa')
}
}
}
const handle2FA = async (e: React.FormEvent) => {
e.preventDefault()
await signIn.complete2FA({ challengeToken, code: twoFactorCode })
// Success - redirect to dashboard
}
if (step === '2fa') {
return (
<form onSubmit={handle2FA}>
<h2>Enter Authentication Code</h2>
<input
type="text"
placeholder="000000"
value={twoFactorCode}
onChange={e => setTwoFactorCode(e.target.value)}
maxLength={6}
autoFocus
/>
<button type="submit">Verify</button>
</form>
)
}
return (
<form onSubmit={handleCredentials}>
<input type="email" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
<button type="submit">Sign In</button>
</form>
)
}Backup Codes
Backup codes allow users to access their account if they lose their authenticator device:
// Using a backup code to sign in
await signIn.complete2FA({
challengeToken,
backupCode: 'XXXX-XXXX', // Backup code instead of TOTP code
})
// Regenerate backup codes
const { backupCodes } = await twoFactor.regenerateBackupCodes()Backup Code Usage
Disable 2FA
Users can disable 2FA after re-verifying their identity:
'use client'
import { useAuth } from '@sylphx/platform-sdk/react'
import { useState } from 'react'
export function Disable2FA() {
const { twoFactor } = useAuth()
const [code, setCode] = useState('')
const handleDisable = async () => {
await twoFactor.disable(code)
// 2FA is now disabled
}
return (
<div>
<h2>Disable Two-Factor Authentication</h2>
<input type="text" value={code} onChange={e => setCode(e.target.value)} placeholder="Enter 6-digit code" />
<button onClick={handleDisable}>Disable 2FA</button>
</div>
)
}Check 2FA Status
Check if 2FA is enabled for the current user:
import { useUser } from '@sylphx/platform-sdk/react'
function SecuritySettings() {
const { user } = useUser()
return (
<div>
<h2>Security Settings</h2>
<p>
Two-Factor Authentication:{' '}
{user?.twoFactorEnabled ? (
<span className="text-green-600">Enabled</span>
) : (
<span className="text-gray-500">Disabled</span>
)}
</p>
</div>
)
}Best Practices
| Property | Type | Description |
|---|---|---|
Encourage 2FA | Recommended | Prompt users to enable 2FA after signup, especially for sensitive apps |
Require for admins | Recommended | Enforce 2FA for admin accounts |
Backup codes | Required | Always provide backup codes and remind users to save them |
Recovery options | Required | Have a support process for users who lose both device and backup codes |
Rate limiting | Automatic | Platform automatically rate-limits 2FA attempts |