# 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?sdk=expo) or [prebuilt components](https://clerk.com/docs/expo/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?sdk=expo).

If you have [Client Trust](https://clerk.com/docs/guides/secure/client-trust.md?sdk=expo) 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?sdk=expo) 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?sdk=expo) 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/(auth)/sign-in.tsx
```tsx
  import { ThemedText } from '@/components/themed-text'
  import { ThemedView } from '@/components/themed-view'
  import { useSignIn } from '@clerk/expo'
  import { type Href, Link, useRouter } from 'expo-router'
  import React from 'react'
  import { Pressable, StyleSheet, TextInput, View } from 'react-native'

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

    const [emailAddress, setEmailAddress] = React.useState('')
    const [password, setPassword] = React.useState('')
    const [code, setCode] = React.useState('')

    const handleSubmit = async () => {
      const { error } = await signIn.password({
        emailAddress,
        password,
      })
      if (error) {
        console.error(JSON.stringify(error, null, 2))
        return
      }

      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 as Href)
            }
          },
        })
      } 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 () => {
+     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 as Href)
+           }
+         },
+       })
+     } else {
+       // Check why the sign-in is not complete
+       console.error('Sign-in attempt not complete:', signIn)
+     }
+   }
+ 
+   if (signIn.status === 'needs_client_trust') {
+     return (
+       <ThemedView style={styles.container}>
+         <ThemedText type="title" style={styles.title}>
+           Verify your account
+         </ThemedText>
+         <TextInput
+           style={styles.input}
+           value={code}
+           placeholder="Enter your verification code"
+           placeholderTextColor="#666666"
+           onChangeText={(code) => setCode(code)}
+           keyboardType="numeric"
+         />
+         {errors.fields.code && (
+           <ThemedText style={styles.error}>{errors.fields.code.message}</ThemedText>
+         )}
+         <Pressable
+           style={({ pressed }) => [
+             styles.button,
+             fetchStatus === 'fetching' && styles.buttonDisabled,
+             pressed && styles.buttonPressed,
+           ]}
+           onPress={handleVerify}
+           disabled={fetchStatus === 'fetching'}
+         >
+           <ThemedText style={styles.buttonText}>Verify</ThemedText>
+         </Pressable>
+         <Pressable
+           style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}
+           onPress={() => signIn.mfa.sendEmailCode()}
+         >
+           <ThemedText style={styles.secondaryButtonText}>I need a new code</ThemedText>
+         </Pressable>
+       </ThemedView>
+     )
+   }

    return (
      <ThemedView style={styles.container}>
        <ThemedText type="title" style={styles.title}>
          Sign in
        </ThemedText>
        <ThemedText style={styles.label}>Email address</ThemedText>
        <TextInput
          style={styles.input}
          autoCapitalize="none"
          value={emailAddress}
          placeholder="Enter email"
          placeholderTextColor="#666666"
          onChangeText={(emailAddress) => setEmailAddress(emailAddress)}
          keyboardType="email-address"
        />
        {errors.fields.identifier && (
          <ThemedText style={styles.error}>{errors.fields.identifier.message}</ThemedText>
        )}
        <ThemedText style={styles.label}>Password</ThemedText>
        <TextInput
          style={styles.input}
          value={password}
          placeholder="Enter password"
          placeholderTextColor="#666666"
          secureTextEntry={true}
          onChangeText={(password) => setPassword(password)}
        />
        {errors.fields.password && (
          <ThemedText style={styles.error}>{errors.fields.password.message}</ThemedText>
        )}
        <Pressable
          style={({ pressed }) => [
            styles.button,
            (!emailAddress || !password || fetchStatus === 'fetching') && styles.buttonDisabled,
            pressed && styles.buttonPressed,
          ]}
          onPress={handleSubmit}
          disabled={!emailAddress || !password || fetchStatus === 'fetching'}
        >
          <ThemedText style={styles.buttonText}>Continue</ThemedText>
        </Pressable>
        {/* For your debugging purposes. You can just console.log errors, but we put them in the UI for convenience */}
        {errors && <ThemedText style={styles.debug}>{JSON.stringify(errors, null, 2)}</ThemedText>}

        <View style={styles.linkContainer}>
          <ThemedText>Don't have an account? </ThemedText>
          <Link href="/sign-up">
            <ThemedText type="link">Sign up</ThemedText>
          </Link>
        </View>
      </ThemedView>
    )
  }

  const styles = StyleSheet.create({
    container: {
      flex: 1,
      padding: 20,
      gap: 12,
    },
    title: {
      marginBottom: 8,
    },
    label: {
      fontWeight: '600',
      fontSize: 14,
    },
    input: {
      borderWidth: 1,
      borderColor: '#ccc',
      borderRadius: 8,
      padding: 12,
      fontSize: 16,
      backgroundColor: '#fff',
    },
    button: {
      backgroundColor: '#0a7ea4',
      paddingVertical: 12,
      paddingHorizontal: 24,
      borderRadius: 8,
      alignItems: 'center',
      marginTop: 8,
    },
    buttonPressed: {
      opacity: 0.7,
    },
    buttonDisabled: {
      opacity: 0.5,
    },
    buttonText: {
      color: '#fff',
      fontWeight: '600',
    },
    secondaryButton: {
      paddingVertical: 12,
      paddingHorizontal: 24,
      borderRadius: 8,
      alignItems: 'center',
      marginTop: 8,
    },
    secondaryButtonText: {
      color: '#0a7ea4',
      fontWeight: '600',
    },
    linkContainer: {
      flexDirection: 'row',
      gap: 4,
      marginTop: 12,
      alignItems: 'center',
    },
    error: {
      color: '#d32f2f',
      fontSize: 12,
      marginTop: -8,
    },
    debug: {
      fontSize: 10,
      opacity: 0.5,
      marginTop: 8,
    },
  })
```

**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/(auth)/sign-in.tsx
```tsx
  import { ThemedText } from '@/components/themed-text'
  import { ThemedView } from '@/components/themed-view'
  import { Colors } from '@/constants/theme'
  import { useColorScheme } from '@/hooks/use-color-scheme'
  import { useSignIn } from '@clerk/expo'
  import { type Href, useRouter } from 'expo-router'
  import React from 'react'
  import { Pressable, StyleSheet, TextInput } from 'react-native'

  export default function Page() {
    const { signIn, errors, fetchStatus } = useSignIn()
    const router = useRouter()
    const colorScheme = useColorScheme() ?? 'light'
    const themeColors = Colors[colorScheme]

    const [phoneNumber, setPhoneNumber] = React.useState('')
    const [password, setPassword] = React.useState('')
    const [code, setCode] = React.useState('')

    const handleSubmit = async () => {
      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 as Href)
            }
          },
        })
      } 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 () => {
+     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 as Href)
+           }
+         },
+       })
+     } else {
+       // Check why the sign-in is not complete
+       console.error('Sign-in attempt not complete:', signIn)
+     }
+   }

+   if (signIn.status === 'needs_client_trust') {
+     return (
+       <ThemedView style={styles.container}>
+         <ThemedText type="title" style={styles.title}>
+           Verify your account
+         </ThemedText>
+         <ThemedText style={styles.label}>Code</ThemedText>
+         <TextInput
+           style={[
+             styles.input,
+             { backgroundColor: themeColors.background, borderColor: themeColors.icon },
+           ]}
+           value={code}
+           placeholder="Enter code"
+           placeholderTextColor={themeColors.icon}
+           onChangeText={(code) => setCode(code)}
+           keyboardType="numeric"
+         />
+         {errors.fields.code && (
+           <ThemedText style={styles.error}>{errors.fields.code.message}</ThemedText>
+         )}
+         <Pressable
+           style={({ pressed }) => [
+             styles.button,
+             fetchStatus === 'fetching' && styles.buttonDisabled,
+             pressed && styles.buttonPressed,
+           ]}
+           onPress={handleVerify}
+           disabled={fetchStatus === 'fetching'}
+         >
+           <ThemedText style={styles.buttonText}>Verify</ThemedText>
+         </Pressable>
+         <Pressable
+           style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}
+           onPress={() => signIn.mfa.sendPhoneCode()}
+         >
+           <ThemedText style={styles.secondaryButtonText}>I need a new code</ThemedText>
+         </Pressable>
+         <Pressable
+           style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}
+           onPress={() => signIn.reset()}
+         >
+           <ThemedText style={styles.secondaryButtonText}>Start over</ThemedText>
+         </Pressable>
+         {errors && <ThemedText style={styles.debug}>{JSON.stringify(errors, null, 2)}</ThemedText>}
+       </ThemedView>
+     )
+   }

    return (
      <ThemedView style={styles.container}>
        <ThemedText type="title" style={styles.title}>
          Sign in
        </ThemedText>
        <ThemedText style={styles.label}>Enter phone number</ThemedText>
        <TextInput
          style={[
            styles.input,
            { backgroundColor: themeColors.background, borderColor: themeColors.icon },
          ]}
          value={phoneNumber}
          placeholder="Enter phone number"
          placeholderTextColor={themeColors.icon}
          onChangeText={(phoneNumber) => setPhoneNumber(phoneNumber)}
          keyboardType="phone-pad"
        />
        {errors.fields.identifier && (
          <ThemedText style={styles.error}>{errors.fields.identifier.message}</ThemedText>
        )}
        <ThemedText style={styles.label}>Enter password</ThemedText>
        <TextInput
          style={[
            styles.input,
            { backgroundColor: themeColors.background, borderColor: themeColors.icon },
          ]}
          value={password}
          placeholder="Enter password"
          placeholderTextColor={themeColors.icon}
          secureTextEntry
          onChangeText={(password) => setPassword(password)}
        />
        {errors.fields.password && (
          <ThemedText style={styles.error}>{errors.fields.password.message}</ThemedText>
        )}
        <Pressable
          style={({ pressed }) => [
            styles.button,
            fetchStatus === 'fetching' && styles.buttonDisabled,
            pressed && styles.buttonPressed,
          ]}
          onPress={handleSubmit}
          disabled={fetchStatus === 'fetching'}
        >
          <ThemedText style={styles.buttonText}>Continue</ThemedText>
        </Pressable>
        {errors && <ThemedText style={styles.debug}>{JSON.stringify(errors, null, 2)}</ThemedText>}
      </ThemedView>
    )
  }

  const styles = StyleSheet.create({
    container: {
      flex: 1,
      padding: 20,
      gap: 12,
    },
    title: {
      marginBottom: 8,
    },
    label: {
      fontWeight: '600',
      fontSize: 14,
    },
    input: {
      borderWidth: 1,
      borderRadius: 8,
      padding: 12,
      fontSize: 16,
    },
    button: {
      backgroundColor: '#0a7ea4',
      paddingVertical: 12,
      paddingHorizontal: 24,
      borderRadius: 8,
      alignItems: 'center',
      marginTop: 8,
    },
    buttonPressed: {
      opacity: 0.7,
    },
    buttonDisabled: {
      opacity: 0.5,
    },
    buttonText: {
      color: '#fff',
      fontWeight: '600',
    },
    secondaryButton: {
      paddingVertical: 12,
      paddingHorizontal: 24,
      borderRadius: 8,
      alignItems: 'center',
      marginTop: 8,
    },
    secondaryButtonText: {
      color: '#0a7ea4',
      fontWeight: '600',
    },
    error: {
      color: '#d32f2f',
      fontSize: 12,
      marginTop: -8,
    },
    debug: {
      fontSize: 10,
      opacity: 0.5,
      marginTop: 8,
    },
  })
```

---

## Sitemap

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