# Build a custom sign-in flow with client trust

> 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 [Client Trust](https://clerk.com/docs/guides/secure/client-trust.md) enabled for your application, when a user is signing in **with a password** on a new client (e.g. device), **the sign-in attempt will return a status of `needs_client_trust`**. Your custom sign-in flow needs to support handling either an email code or SMS code, depending on the settings you've enabled in the Clerk Dashboard.

If Client Trust **and** MFA are enabled, MFA will take precedence and the sign-in attempt will return a status of `needs_second_factor`. See the [MFA custom flow guide](https://clerk.com/docs/guides/development/custom-flows/authentication/multi-factor-authentication.md) for more information.

**Email code**

### Configure application settings

**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. This is the option you'll want for this example.
3. Enable **Sign in with email**.
4. Select the **Password** tab and enable **Sign-up with password**.

### Build the custom flow

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()

    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 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') {
        // See https://clerk.com/docs/guides/development/custom-flows/authentication/multi-factor-authentication
+     } else if (signIn.status === 'needs_client_trust') {
+       const emailCodeFactor = signIn.supportedSecondFactors.find(
+         (factor) => factor.strategy === 'email_code',
+       )
+ 
+       if (emailCodeFactor) {
+         await signIn.mfa.sendEmailCode()
+       }
      } else {
        // Check why the sign-in is not complete
        console.error('Sign-in attempt not complete:', signIn)
      }
    }

    const handleVerify = async (formData: FormData) => {
      const code = formData.get('code') as string

      await signIn.mfa.verifyEmailCode({ code })

      if (signIn.status === 'complete') {
        await signIn.finalize({
          navigate: ({ session, decorateUrl }) => {
            // Handle 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)
            }
          },
        })
      }
    }

+   if (signIn.status === 'needs_client_trust') {
+     return (
+       <>
+         <h1>Verify your account</h1>
+         <form action={handleVerify}>
+           <div>
+             <label htmlFor="code">Code</label>
+             <input id="code" name="code" type="text" />
+             {errors.fields.code && <p>{errors.fields.code.message}</p>}
+           </div>
+           <button type="submit" disabled={fetchStatus === 'fetching'}>
+             Verify
+           </button>
+         </form>
+         <button onClick={() => signIn.mfa.sendEmailCode()}>I need a new code</button>
+         <button onClick={() => signIn.reset()}>Start over</button>
+         {/* 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>}
+       </>
+     )
+   }

    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>}
      </>
    )
  }
```

**SMS code**

### Configure application settings

Client Trust will fallback to SMS verification code **only if email is completely disabled for the application**, or else Client Trust will fallback to either email code or email link, depending on your application's settings.

1. In the Clerk Dashboard, navigate to the [**User & authentication**](https://dashboard.clerk.com/~/user-authentication/user-and-authentication) page.
2. Ensure all email options are disabled.
3. Select the **Phone** tab. Ensure all the settings are enabled.
4. Select the **Password** tab and enable **Sign-up with password**. **Client Trust** is enabled by default.

### Build the custom flow

filename: app/sign-in/page.tsx
```tsx
  'use client'

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

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

    const handleSubmit = async (formData: FormData) => {
      const phoneNumber = formData.get('phoneNumber') as string
      const password = formData.get('password') as string

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

      if (signIn.status === 'complete') {
        await signIn.finalize({
          navigate: ({ session, decorateUrl }) => {
            // Handle 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') {
        // See https://clerk.com/docs/guides/development/custom-flows/authentication/multi-factor-authentication
+     } else if (signIn.status === 'needs_client_trust') {
+       const phoneCodeFactor = signIn.supportedSecondFactors.find(
+         (factor): factor is PhoneCodeFactor => factor.strategy === 'phone_code',
+       )
+ 
+       if (phoneCodeFactor) {
+         await signIn.mfa.sendPhoneCode()
+       }
      } else {
        // Check why the sign-in is not complete
        console.error('Sign-in attempt not complete:', signIn)
      }
    }

    const handleVerify = async (formData: FormData) => {
      const code = formData.get('code') as string

      await signIn.mfa.verifyPhoneCode({ code })

      if (signIn.status === 'complete') {
        await signIn.finalize({
          navigate: ({ session, decorateUrl }) => {
            // Handle 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)
            }
          },
        })
      }
    }

+   if (signIn.status === 'needs_client_trust') {
+     return (
+       <>
+         <h1>Verify your account</h1>
+         <form action={handleVerify}>
+           <div>
+             <label htmlFor="code">Code</label>
+             <input id="code" name="code" type="text" />
+             {errors.fields.code && <p>{errors.fields.code.message}</p>}
+           </div>
+           <button type="submit" disabled={fetchStatus === 'fetching'}>
+             Verify
+           </button>
+         </form>
+         <button onClick={() => signIn.mfa.sendPhoneCode()}>I need a new code</button>
+         <button onClick={() => signIn.reset()}>Start over</button>
+         {/* 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>}
+       </>
+     )
+   }

    return (
      <>
        <h1>Sign in</h1>
        <form action={handleSubmit}>
          <div>
            <label htmlFor="phoneNumber">Enter phone number</label>
            <input id="phoneNumber" name="phoneNumber" type="tel" />
            {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>}
      </>
    )
  }
```

---

## Sitemap

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