# Build a custom sign-in flow with multi-factor authentication (MFA)

> This guide is for users who want to build a custom flow. To use a _prebuilt_ UI, use the [Account Portal pages](https://clerk.com/docs/guides/account-portal/overview.md) or [prebuilt components](https://clerk.com/docs/reference/components/overview.md).

> This guide applies to the following Clerk SDKs:
>
> - `@clerk/react` v6 or higher
> - `@clerk/nextjs` v7 or higher
> - `@clerk/expo` v3 or higher
> - `@clerk/react-router` v3 or higher
> - `@clerk/tanstack-react-start` v0.26.0 or higher
>
> If you're using an older version of one of these SDKs, or are using the legacy API, refer to the [legacy API documentation](https://clerk.com/docs/guides/development/custom-flows/authentication/legacy/email-password-mfa.md).

If you have [multi-factor authentication (MFA)](https://clerk.com/docs/guides/configure/auth-strategies/sign-up-sign-in-options.md#multi-factor-authentication) enabled for your application, **the sign-in attempt will return a status of `needs_second_factor`**. Your custom sign-in flow needs to support handling whichever second factor strategy you've enabled in the Clerk Dashboard.

Clerk allows you to enable the following second factor strategies:

- MFA:
  - SMS verification code
  - Authenticator application
  - Backup codes (but one of the other strategies must be enabled)

This guide will demonstrate how to build a custom user interface for adding MFA to your sign-in flow.

1. ### Enable email and password

   **This example uses the [email and password sign-in custom flow](https://clerk.com/docs/guides/development/custom-flows/authentication/email-password.md) as a base. However, you can modify this approach according to the settings you've configured for your application's instance in the Clerk Dashboard.**

   1. In the Clerk Dashboard, navigate to the [**User & authentication**](https://dashboard.clerk.com/~/user-authentication/user-and-authentication) page.
   2. Enable **Sign-up with email**.
      - **Require email address** should be enabled.
      - For **Verify at sign-up**, **Email verification code** is enabled by default, and is used for this guide. If you'd like to use **Email verification link** instead, see the [dedicated custom flow](https://clerk.com/docs/guides/development/custom-flows/authentication/email-links.md).
   3. Enable **Sign in with email**.
      - This guide supports password authentication. If you'd like to build a custom flow that allows users to sign in passwordlessly, see the [email code custom flow](https://clerk.com/docs/guides/development/custom-flows/authentication/email-sms-otp.md) or the [email links custom flow](https://clerk.com/docs/guides/development/custom-flows/authentication/email-links.md).
   4. Select the **Password** tab and enable **Sign-up with password**.
      - [**Client Trust**](https://clerk.com/docs/guides/secure/client-trust.md) is enabled by default. The sign-in example supports it using email verification codes because it's the default second factor strategy.
2. ### Enable multi-factor authentication (MFA)

   1. In the Clerk Dashboard, navigate to the [**Multi-factor**](https://dashboard.clerk.com/~/user-authentication/multi-factor) page.
   2. Enable the strategy you want to use for your second factor.
      - To enable **SMS verification code**, you'll need to enable **Sign-up with phone** and **Sign-in with phone**. It's highly recommended to enable **Verify at sign-up** for phone numbers.
   3. **Require multi-factor authentication** is enabled by default. You will need to handle the `setup-mfa` session task. See the [dedicated custom flow](https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks.md) for more information.
   4. Select **Save**.

   > For this example to work, the user must have MFA enabled on their account. You need to add the ability for your users to manage their MFA settings. See the [dedicated custom flow](https://clerk.com/docs/guides/development/custom-flows/account-updates/manage-mfa.md).
3. ### Build the custom flow

   The following example demonstrates how to build a custom sign-in flow that supports SMS verification codes, authenticator app codes, and backup codes. Essentially, you want to:

   1. Check the `signIn.status` to see if it's `needs_second_factor`.
   2. If it is, display a form to collect the MFA code.
   3. If the user submits the form, verify the code with the appropriate method: [signIn.mfa.verifyPhoneCode()](https://clerk.com/docs/reference/objects/sign-in-future.md#mfa-verify-phone-code), [signIn.mfa.verifyTOTP()](https://clerk.com/docs/reference/objects/sign-in-future.md#mfa-verify-totp), or [signIn.mfa.verifyBackupCode()](https://clerk.com/docs/reference/objects/sign-in-future.md#mfa-verify-backup-code).
   4. If the verification is successful (the `signIn.status` is `'complete'`), call [signIn.finalize()](https://clerk.com/docs/reference/objects/sign-in-future.md#finalize) to set the newly created session as the active session.

   filename: app/sign-in/page.tsx

   ```tsx
     'use client'

     import { useSignIn } from '@clerk/nextjs'
     import { useRouter } from 'next/navigation'

     export default function Page() {
       const { signIn, errors, fetchStatus } = useSignIn()
       const router = useRouter()

       // Step 1: Create the sign-in
       const handleSubmit = async (formData: FormData) => {
         const emailAddress = formData.get('email') as string
         const password = formData.get('password') as string

         await signIn.password({
           emailAddress,
           password,
         })

         if (signIn.status === 'complete') {
           await signIn.finalize({
             navigate: ({ session, decorateUrl }) => {
               // Handle pending session tasks
               // See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
               if (session?.currentTask) {
                 console.log(session?.currentTask)
                 return
               }

               // If no session tasks, navigate the signed-in user to the home page
               const url = decorateUrl('/')
               if (url.startsWith('http')) {
                 window.location.href = url
               } else {
                 router.push(url)
               }
             },
           })
   +     } else if (signIn.status === 'needs_second_factor') {
   +       await signIn.mfa.sendPhoneCode()
         } else if (signIn.status === 'needs_client_trust') {
           // See https://clerk.com/docs/guides/development/custom-flows/authentication/client-trust
         } else {
           // Check why the sign-in is not complete
           console.error('Sign-in attempt not complete:', signIn)
         }
       }

   +   // Step 2: Handle the MFA verification
   +   const handleMFAVerification = async (formData: FormData) => {
   +     const code = formData.get('code') as string
   +     const useBackupCode = formData.get('useBackupCode') === 'on'
   + 
   +     if (useBackupCode) {
   +       await signIn.mfa.verifyBackupCode({ code })
   +     } else {
   +       await signIn.mfa.verifyPhoneCode({ code })
   +       // If you're using the authenticator app strategy, use the following method instead:
   +       // await signIn.mfa.verifyTOTP({ code })
   +     }
   + 
   +     if (signIn.status === 'complete') {
   +       await signIn.finalize({
   +         navigate: ({ session, decorateUrl }) => {
   +           // Handle pending session tasks
   +           // See https://clerk.com/docs/guides/development/custom-flows/authentication/session-tasks
   +           if (session?.currentTask) {
   +             console.log(session?.currentTask)
   +             return
   +           }
   + 
   +           // If no session tasks, navigate the signed-in user to the home page
   +           const url = decorateUrl('/')
   +           if (url.startsWith('http')) {
   +             window.location.href = url
   +           } else {
   +             router.push(url)
   +           }
   +         },
   +       })
   +     }
   +   }
   + 
   +   // Step 2 UI: Display the MFA verification form
   +   if (signIn.status === 'needs_second_factor') {
   +     return (
   +       <div>
   +         <h1>Verify your account</h1>
   +         <form action={handleMFAVerification}>
   +           <div>
   +             <label htmlFor="code">Code</label>
   +             <input id="code" name="code" type="text" />
   +             {errors.fields.code && <p>{errors.fields.code.message}</p>}
   +           </div>
   +           <div>
   +             <label>
   +               Use backup code
   +               <input type="checkbox" name="useBackupCode" />
   +             </label>
   +           </div>
   +           <button type="submit" disabled={fetchStatus === 'fetching'}>
   +             Verify
   +           </button>
   +         </form>
   +       </div>
   +     )
   +   }

       // Step 1 UI: Display the sign-in form
       return (
         <>
           <h1>Sign in</h1>
           <form action={handleSubmit}>
             <div>
               <label htmlFor="email">Enter email address</label>
               <input id="email" name="email" type="email" />
               {errors.fields.identifier && <p>{errors.fields.identifier.message}</p>}
             </div>
             <div>
               <label htmlFor="password">Enter password</label>
               <input id="password" name="password" type="password" />
               {errors.fields.password && <p>{errors.fields.password.message}</p>}
             </div>
             <button type="submit" disabled={fetchStatus === 'fetching'}>
               Continue
             </button>
           </form>
           {/* For your debugging purposes. You can just console.log errors, but we put them in the UI for convenience */}
           {errors && <p>{JSON.stringify(errors, null, 2)}</p>}
         </>
       )
     }
   ```

## Next steps

Now that users can sign in with MFA, you need to add the ability for your users to manage their MFA settings. Learn how to build a custom flow for [managing MFA settings](https://clerk.com/docs/guides/development/custom-flows/account-updates/manage-mfa.md).

---

## Sitemap

[Overview of all docs pages](https://clerk.com/docs/llms.txt)
