# Build a custom email/password authentication flow

> 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.md).

This guide demonstrates how to build a custom user interface for signing up and signing in using email and password.

1. ## Enable email and password authentication

   To follow this guide, you first need to ensure email and password are enabled for your application.

   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. ## Sign-up flow

   First, understand that the `useSignUp()` hook returns an object with the following properties:

   - `signUp`: The [SignUpFuture](https://clerk.com/docs/reference/objects/sign-up-future.md) object. Use this to initiate the sign-up process and check the current state of the sign-up attempt.
   - `errors`: The [Errors](https://clerk.com/docs/reference/types/errors.md) object that contains the errors that occurred during the last API request. You can use this to display errors to the user in your custom UI.
   - `fetchStatus`: The fetch status of the underlying [SignUpFuture](https://clerk.com/docs/reference/objects/sign-up-future.md) resource. You can use this to display a loading state or disable buttons while the request is in progress.

   Then, to sign up a user using their email and password, and verify their sign-up with an email verification code, you must:

   1. Initiate the sign-up process by collecting the user's email address and password with the [signUp.password()](https://clerk.com/docs/reference/objects/sign-up-future.md#password) method.
   2. Send a one-time code to the provided email address with the [signUp.verifications.sendEmailCode()](https://clerk.com/docs/reference/objects/sign-up-future.md#verifications-send-email-code) method.
   3. Collect the user's one-time code and verify it with the [signUp.verifications.verifyEmailCode()](https://clerk.com/docs/reference/objects/sign-up-future.md#verifications-verify-email-code) method.
   4. If the email address verification is successful, the `signUp.status` will be `complete`, and you can finish the sign-up flow with the [signUp.finalize()](https://clerk.com/docs/reference/objects/sign-up-future.md#finalize) method to set the newly created session as the active session.

   filename: app/sign-up/page.tsx

   ```tsx
   'use client'

   import { useAuth, useSignUp } from '@clerk/nextjs'
   import { useRouter } from 'next/navigation'

   export default function Page() {
     const { signUp, errors, fetchStatus } = useSignUp()
     const { isSignedIn } = useAuth()
     const router = useRouter()

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

       const { error } = await signUp.password({
         emailAddress,
         password,
       })
       if (error) {
         // See https://clerk.com/docs/guides/development/custom-flows/error-handling
         // for more info on error handling
         console.error(JSON.stringify(error, null, 2))
         return
       }

       if (!error) await signUp.verifications.sendEmailCode()
     }

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

       await signUp.verifications.verifyEmailCode({
         code,
       })
       if (signUp.status === 'complete') {
         await signUp.finalize({
           // Redirect the user to the home page after signing up
           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 {
         // Check why the sign-up is not complete
         console.error('Sign-up attempt not complete:', signUp)
       }
     }

     if (signUp.status === 'complete' || isSignedIn) {
       return null
     }

     if (
       signUp.status === 'missing_requirements' &&
       signUp.unverifiedFields.includes('email_address') &&
       signUp.missingFields.length === 0
     ) {
       return (
         <>
           <h1>Verify your account</h1>
           <form action={handleVerify}>
             <div>
               <label htmlFor="code">Code</label>
               <input id="code" name="code" type="text" />
             </div>
             {errors.fields.code && <p>{errors.fields.code.message}</p>}
             <button type="submit" disabled={fetchStatus === 'fetching'}>
               Verify
             </button>
           </form>
           <button onClick={() => signUp.verifications.sendEmailCode()}>I need a new code</button>
         </>
       )
     }

     return (
       <>
         <h1>Sign up</h1>
         <form action={handleSubmit}>
           <div>
             <label htmlFor="email">Enter email address</label>
             <input id="email" type="email" name="email" />
             {errors.fields.emailAddress && <p>{errors.fields.emailAddress.message}</p>}
           </div>
           <div>
             <label htmlFor="password">Enter password</label>
             <input id="password" type="password" name="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>}

         {/* Required for sign-up flows. Clerk's bot sign-up protection is enabled by default */}
         <div id="clerk-captcha" />
       </>
     )
   }
   ```
3. ## Sign-in flow

   First, understand that the `useSignIn()` hook returns an object with the following properties:

   - `signIn`: The [SignInFuture](https://clerk.com/docs/reference/objects/sign-in-future.md) object. Use this to initiate the sign-in process and check the current state of the sign-in attempt.
   - `errors`: The [Errors](https://clerk.com/docs/reference/types/errors.md) object that contains the errors that occurred during the last API request. You can use this to display errors to the user in your custom UI.
   - `fetchStatus`: The fetch status of the underlying [SignInFuture](https://clerk.com/docs/reference/objects/sign-in-future.md) resource. You can use this to display a loading state or disable buttons while the request is in progress.

   Then, to sign in a user using their email and password, you must:

   1. Initiate the sign-in process by collecting the user's email address and password with the [signIn.password()](https://clerk.com/docs/reference/objects/sign-in-future.md#password) method.
   2. If the `signIn.status` is `'needs_second_factor'`, the user has MFA enabled. See the [MFA custom flow guide](https://clerk.com/docs/guides/development/custom-flows/authentication/multi-factor-authentication.md) for how to handle this status. If the status is `'needs_client_trust'`, see the [Client Trust custom flow guide](https://clerk.com/docs/guides/development/custom-flows/authentication/client-trust.md).
   3. If the `signIn.status` is `'complete'`, finish the sign-in flow with the [signIn.finalize()](https://clerk.com/docs/reference/objects/sign-in-future.md#finalize) method 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()

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

       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)
             }
           },
         })
       } 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') {
         // For other second factor strategies,
         // see https://clerk.com/docs/guides/development/custom-flows/authentication/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)
             }
           },
         })
       } else {
         // Check why the sign-in is not complete
         console.error('Sign-in attempt not complete:', signIn)
       }
     }

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

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

---

## Sitemap

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