Build a custom flow for managing TOTP-based multi-factor authentication
Multi-factor verification (MFA) is an added layer of security that requires users to provide a second verification factor to access an account.
One of the options that Clerk supports for MFA is Authenticator applications (also known as TOTP - Time-based One-time Password). This guide will walk you through how to build a custom flow that allows users to manage their TOTP settings.
Enable multi-factor authentication
For your users to be able to enable MFA for their account, you need to enable MFA as an MFA authentication strategy in your Clerk application.
- In the Clerk Dashboard, navigate to the Multi-factor page.
- Enable Authenticator application and Backup codes.
- Select Save.
Create the multi-factor management flow
This example is written for Next.js App Router but it can be adapted for any React-based framework.
This example consists of two pages:
- The main page where users can manage their MFA settings
- The page where users can add TOTP MFA.
Use the following tabs to view the code necessary for each page.
'use client'
import * as React from 'react'
import { useUser, useReverification } from '@clerk/nextjs'
import Link from 'next/link'
import { BackupCodeResource } from '@clerk/types'
// If TOTP is enabled, provide the option to disable it
const TotpEnabled = () => {
const { user } = useUser()
const disableTOTP = useReverification(() => user?.disableTOTP())
return (
<div>
<p>
TOTP via authentication app enabled - <button onClick={() => disableTOTP()}>Remove</button>
</p>
</div>
)
}
// If TOTP is disabled, provide the option to enable it
const TotpDisabled = () => {
return (
<div>
<p>
Add TOTP via authentication app -{' '}
<Link href="/account/manage-mfa/add">
<button>Add</button>
</Link>
</p>
</div>
)
}
// Generate and display backup codes
export function GenerateBackupCodes() {
const { user } = useUser()
const [backupCodes, setBackupCodes] = React.useState<BackupCodeResource | undefined>(undefined)
const createBackupCode = useReverification(() => user?.createBackupCode())
const [loading, setLoading] = React.useState(false)
React.useEffect(() => {
if (backupCodes) {
return
}
setLoading(true)
void createBackupCode()
.then((backupCode: BackupCodeResource | undefined) => {
setBackupCodes(backupCode)
setLoading(false)
})
.catch((err) => {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
setLoading(false)
})
}, [])
if (loading) {
return <p>Loading...</p>
}
if (!backupCodes) {
return <p>There was a problem generating backup codes</p>
}
return (
<ol>
{backupCodes.codes.map((code, index) => (
<li key={index}>{code}</li>
))}
</ol>
)
}
export default function ManageMFA() {
const { isLoaded, user } = useUser()
const [showNewCodes, setShowNewCodes] = React.useState(false)
if (!isLoaded) return null
if (!user) {
return <p>You must be logged in to access this page</p>
}
return (
<>
<h1>User MFA Settings</h1>
{/* Manage TOTP MFA */}
{user.totpEnabled ? <TotpEnabled /> : <TotpDisabled />}
{/* Manage backup codes */}
{user.backupCodeEnabled && user.twoFactorEnabled && (
<div>
<p>
Generate new backup codes? -{' '}
<button onClick={() => setShowNewCodes(true)}>Generate</button>
</p>
</div>
)}
{showNewCodes && (
<>
<GenerateBackupCodes />
<button onClick={() => setShowNewCodes(false)}>Done</button>
</>
)}
</>
)
}
Feedback
Last updated on