Skip to main content
Docs

Build a custom flow for managing TOTP-based multi-factor authentication

Warning

This guide is for users who want to build a custom user interface using the Clerk API. To use a prebuilt UI, use the Account Portal pages or prebuilt components.

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.

Tip

To learn how to build a custom flow for managing SMS MFA, see the dedicated guide.

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.

  1. In the Clerk Dashboard, navigate to the Multi-factor page.
  2. Enable Authenticator application and Backup codes.
  3. 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.

app/account/manage-mfa/page.tsx
'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

What did you think of this content?

Last updated on