šŸ“ Sign Up | šŸ” Log In

← Root | ↑ Up

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ šŸ“„ shadcn/directory/clerk/clerk-docs/guides/development/custom-flows/account-updates/manage-sms-based-mfa │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

╔══════════════════════════════════════════════════════════════════════════════════════════════╗
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘

title: Build a custom flow for managing SMS-based multi-factor authentication description: Learn how to use the Clerk API to build a custom flow for managing SMS-based multi-factor authentication.

<Include src="_partials/custom-flows-callout" />

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 SMS verification codes. 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 authenticator app (TOTP) MFA, see the dedicated guide.

<Steps> ## 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 SMS verification code and Backup codes and 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 SMS MFA settings.
  • The page where users can add a phone number to their account.

Use the following tabs to view the code necessary for each page.

<Include src="_partials/custom-flows/phone-number" />

<Tabs items={["Manage MFA page", "Add phone number page"]}> <Tab> ```tsx {{ filename: 'app/account/manage-mfa/page.tsx', collapsible: true }} 'use client'

  import * as React from 'react'
  import { useUser, useReverification } from '@clerk/nextjs'
  import { BackupCodeResource, PhoneNumberResource } from '@clerk/types'
  import Link from 'next/link'

  // Display phone numbers reserved for MFA
  const ManageMfaPhoneNumbers = () => {
    const { isSignedIn, user } = useUser()

    if (!isSignedIn) {
      // Handle signed out state
      return null
    }

    // Check if any phone numbers are reserved for MFA
    const mfaPhones = user.phoneNumbers
      .filter((ph) => ph.verification.status === 'verified')
      .filter((ph) => ph.reservedForSecondFactor)
      .sort((ph: PhoneNumberResource) => (ph.defaultSecondFactor ? -1 : 1))

    if (mfaPhones.length === 0) {
      return <p>There are currently no phone numbers reserved for MFA.</p>
    }

    return (
      <>
        <h2>Phone numbers reserved for MFA</h2>
        <ul>
          {mfaPhones.map((phone) => {
            return (
              <li key={phone.id} style={{ display: 'flex', gap: '10px' }}>
                <p>
                  {phone.phoneNumber} {phone.defaultSecondFactor && '(Default)'}
                </p>
                <div>
                  <button onClick={() => phone.setReservedForSecondFactor({ reserved: false })}>
                    Disable for MFA
                  </button>
                </div>

                {!phone.defaultSecondFactor && (
                  <div>
                    <button onClick={() => phone.makeDefaultSecondFactor()}>Make default</button>
                  </div>
                )}

                <div>
                  <button onClick={() => phone.destroy()}>Remove from account</button>
                </div>
              </li>
            )
          })}
        </ul>
      </>
    )
  }

  // Display phone numbers that are not reserved for MFA
  const ManageAvailablePhoneNumbers = () => {
    const { isSignedIn, user } = useUser()
    const setReservedForSecondFactor = useReverification((phone: PhoneNumberResource) =>
      phone.setReservedForSecondFactor({ reserved: true }),
    )
    const destroyPhone = useReverification((phone: PhoneNumberResource) => phone.destroy())

    if (!isSignedIn) {
      // Handle signed out state
      return null
    }

    // Check if any phone numbers aren't reserved for MFA
    const availableForMfaPhones = user.phoneNumbers
      .filter((ph) => ph.verification.status === 'verified')
      .filter((ph) => !ph.reservedForSecondFactor)

    // Reserve a phone number for MFA
    const reservePhoneForMfa = async (phone: PhoneNumberResource) => {
      // Set the phone number as reserved for MFA
      await setReservedForSecondFactor(phone)
      // Refresh the user information to reflect changes
      await user.reload()
    }

    if (availableForMfaPhones.length === 0) {
      return <p>There are currently no verified phone numbers available to be reserved for MFA.</p>
    }

    return (
      <>
        <h2>Phone numbers that are not reserved for MFA</h2>

        <ul>
          {availableForMfaPhones.map((phone) => {
            return (
              <li key={phone.id} style={{ display: 'flex', gap: '10px' }}>
                <p>{phone.phoneNumber}</p>
                <div>
                  <button onClick={() => reservePhoneForMfa(phone)}>Use for MFA</button>
                </div>
                <div>
                  <button onClick={() => destroyPhone()}>Remove from account</button>
                </div>
              </li>
            )
          })}
        </ul>
      </>
    )
  }

  // Generate and display backup codes
  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) => {
          setBackupCodes(backupCode)
          setLoading(false)
        })
        .catch((err) => {
          // See https://clerk.com/docs/guides/development/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 ManageSMSMFA() {
    const [showBackupCodes, setShowBackupCodes] = React.useState(false)

    const { isLoaded, isSignedIn, user } = useUser()

    if (!isLoaded) {
      // Handle loading state
      return null
    }

    if (!isSignedIn) {
      // Handle signed out state
      return <p>You must be logged in to access this page</p>
    }

    return (
      <>
        <h1>User MFA Settings</h1>

        {/* Manage SMS MFA */}
        <ManageMfaPhoneNumbers />
        <ManageAvailablePhoneNumbers />
        <Link href="/account/add-phone">Add a new phone number</Link>

        {/* Manage backup codes */}
        {user.twoFactorEnabled && (
          <div>
            <p>
              Generate new backup codes? -{' '}
              <button onClick={() => setShowBackupCodes(true)}>Generate</button>
            </p>
          </div>
        )}
        {showBackupCodes && (
          <>
            <GenerateBackupCodes />
            <button onClick={() => setShowBackupCodes(false)}>Done</button>
          </>
        )}
      </>
    )
  }
  ```
</Tab>

<Tab>
  ```tsx {{ filename: 'app/account/add-phone/page.tsx', collapsible: true }}
  'use client'

  import * as React from 'react'
  import { useUser, useReverification } from '@clerk/nextjs'
  import { PhoneNumberResource } from '@clerk/types'

  export default function Page() {
    const { isLoaded, isSignedIn, user } = useUser()
    const [phone, setPhone] = React.useState('')
    const [code, setCode] = React.useState('')
    const [isVerifying, setIsVerifying] = React.useState(false)
    const [successful, setSuccessful] = React.useState(false)
    const [phoneObj, setPhoneObj] = React.useState<PhoneNumberResource | undefined>()
    const createPhoneNumber = useReverification((phone: string) =>
      user?.createPhoneNumber({ phoneNumber: phone }),
    )

    if (!isLoaded) {
      // Handle loading state
      return null
    }

    if (!isSignedIn) {
      // Handle signed out state
      return <p>You must be logged in to access this page</p>
    }

    // Handle addition of the phone number
    const handleSubmit = async (e: React.FormEvent) => {
      e.preventDefault()

      try {
        // Add unverified phone number to user
        const res = await createPhoneNumber(phone)
        // Reload user to get updated User object
        await user.reload()

        // Create a reference to the new phone number to use related methods
        const phoneNumber = user.phoneNumbers.find((a) => a.id === res.id)
        setPhoneObj(phoneNumber)

        // Send the user an SMS with the verification code
        phoneNumber?.prepareVerification()

        // Set to true to display second form
        // and capture the OTP code
        setIsVerifying(true)
      } catch (err) {
        // See https://clerk.com/docs/guides/development/custom-flows/error-handling
        // for more info on error handling
        console.error(JSON.stringify(err, null, 2))
      }
    }

    // Handle the submission of the verification form
    const verifyCode = async (e: React.FormEvent) => {
      e.preventDefault()
      try {
        // Verify that the provided code matches the code sent to the user
        const phoneVerifyAttempt = await phoneObj?.attemptVerification({ code })

        if (phoneVerifyAttempt?.verification.status === 'verified') {
          setSuccessful(true)
        } else {
          // If the status is not complete, check why. User may need to
          // complete further steps.
          console.error(JSON.stringify(phoneVerifyAttempt, null, 2))
        }
      } catch (err) {
        console.error(JSON.stringify(err, null, 2))
      }
    }

    // Display a success message if the phone number was added successfully
    if (successful) {
      return (
        <>
          <h1>Phone added</h1>
        </>
      )
    }

    // Display the verification form to capture the OTP code
    if (isVerifying) {
      return (
        <>
          <h1>Verify phone</h1>
          <div>
            <form onSubmit={(e) => verifyCode(e)}>
              <div>
                <label htmlFor="code">Enter code</label>
                <input
                  onChange={(e) => setCode(e.target.value)}
                  id="code"
                  name="code"
                  type="text"
                  value={code}
                />
              </div>
              <div>
                <button type="submit">Verify</button>
              </div>
            </form>
          </div>
        </>
      )
    }

    // Display the initial form to capture the phone number
    return (
      <>
        <h1>Add phone</h1>
        <div>
          <form onSubmit={(e) => handleSubmit(e)}>
            <div>
              <label htmlFor="phone">Enter phone number</label>
              <input
                onChange={(e) => setPhone(e.target.value)}
                id="phone"
                name="phone"
                type="phone"
                value={phone}
              />
            </div>
            <div>
              <button type="submit">Continue</button>
            </div>
          </form>
        </div>
      </>
    )
  }
  ```
</Tab>
</Tabs> </Steps>
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•

← Root | ↑ Up