Two-Factor Authentication

TOTP

Add an extra layer of security with TOTP-based two-factor authentication.

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.

Google Authenticator
Authy
1Password

Enable 2FA Flow

1

Generate Secret

Call twoFactor.setup() to generate a secret and QR code URL.

2

Scan QR Code

User scans the QR code with their authenticator app (or enters secret manually).

3

Verify Code

User enters a 6-digit code from their app to verify setup.

4

Save Backup Codes

Display backup codes and remind user to save them securely.

components/enable-2fa.tsx
'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:

components/login-with-2fa.tsx
'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

Each backup code can only be used once. Users should regenerate backup codes if they're running low.

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

PropertyTypeDescription
Encourage 2FARecommendedPrompt users to enable 2FA after signup, especially for sensitive apps
Require for adminsRecommendedEnforce 2FA for admin accounts
Backup codesRequiredAlways provide backup codes and remind users to save them
Recovery optionsRequiredHave a support process for users who lose both device and backup codes
Rate limitingAutomaticPlatform automatically rate-limits 2FA attempts

Enforce 2FA

You can require 2FA for all users or specific roles in your Sylphx Dashboard under App Settings → Security.