Skip to Content
Clerk logo

Clerk Docs

Ctrl + K
Go to clerk.com

Custom email/password flow

Clerk supports password authentication, which allows users to sign up and sign in using their email address and password. This guide will walk you through how to build a custom email/password sign-up and sign-in flow using the useSignUp() and useSignIn() React hooks.

Enable password and email

In the Clerk dashboard, you will need to enable both email and password as a sign-in and sign-up method. Go to User & Authentication > Email, Phone, and Username(opens in a new tab).Toggle on Email address and Password.

The 'Email, Phone, and Username' page in the Clerk dashboard. There is a red arrow pointing the title of the page. There are also red arrows pointing to the toggles for 'Email address' and 'Password', both toggled on.

Create sign up flow

The email/password sign-up flow requires users to provide their email address and their password and returns a newly-created user with an active session.

A successful sign-up consists of the following steps:

  1. Initiate the sign-up process by collecting the user's email address and password.
  2. Prepare the email address verification, which sends a one-time code to the given address.
  3. Attempt to complete the email address verification by supplying the one-time code.
  4. If the email address verification is successful, complete the sign-up process by creating the user account and setting their session as active.
'use client' import * as React from 'react'; import { useSignUp } from '@clerk/nextjs'; import { useRouter } from 'next/navigation'; import { ClerkAPIErrorJSON } from '@clerk/types'; export default function Page() { const { isLoaded, signUp, setActive } = useSignUp(); const [emailAddress, setEmailAddress] = React.useState(""); const [password, setPassword] = React.useState(""); const [verifying, setVerifying] = React.useState(false); const [code, setCode] = React.useState(""); const router = useRouter(); // This function will handle the user submitting their email and password const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!isLoaded) return; // Start the sign-up process using the email and password provided try { await signUp.create({ emailAddress, password }); // Send the user an email with the verification code await signUp.prepareEmailAddressVerification({ strategy: 'email_code' }); // Set 'verifying' true to display second form and capture the OTP code setVerifying(true); } catch (err: any) { // This can return an array of errors. // See https://clerk.com/docs/custom-flows/error-handling to learn about error handling console.error('Error:', JSON.stringify(err, null, 2)); } } // This function will handle the user submitting a code for verification const handleVerify = async (e: React.FormEvent) => { e.preventDefault(); if (!isLoaded) return; try { // Submit the code that the user provides to attempt verification const completeSignUp = await signUp.attemptEmailAddressVerification({ code }); if (completeSignUp.status !== "complete") { // The status can also be `abandoned` or `missing_requirements` // Please see https://clerk.com/docs/references/react/use-sign-up#result-status for more information console.log(JSON.stringify(completeSignUp, null, 2)); } // Check the status to see if it is complete // If complete, the user has been created -- set the session active if (completeSignUp.status === "complete") { await setActive({ session: completeSignUp.createdSessionId }); // Redirect the user to a post sign-up route router.push("/"); } } catch (err: any) { // This can return an array of errors. // See https://clerk.com/docs/custom-flows/error-handling to learn about error handling console.error('Error:', JSON.stringify(err, null, 2)); } } // Once the sign-up form was submitted, verifying was set to true and as a result, this verification form is presented to the user to input their verification code. if (verifying) { return ( <form onSubmit={handleVerify}> <label id="code">Code</label> <input value={code} id="code" name="code" onChange={(e) => setCode(e.target.value)} /> <button type="submit">Complete Sign Up</button> </form> ) } // Display the initial sign-up form to capture the email and password return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="email">Email address</label> <input id="email" type='email' name="email" value={emailAddress} onChange={(e) => setEmailAddress(e.target.value)} /> </div> <div> <label className="block text-sm mt-8" htmlFor="password">Password</label> <input id="password" type='password' name="password" value={password} onChange={(e) => setPassword(e.target.value)} /> </div> <div> <button type="submit">Verify Email</button> </div> </form> ); }
pages/sign-up/[[...index]].tsx
import { useState } from "react"; import { useSignUp } from "@clerk/nextjs"; import { useRouter } from "next/router"; export default function SignUpForm() { const { isLoaded, signUp, setActive } = useSignUp(); const [emailAddress, setEmailAddress] = useState(""); const [password, setPassword] = useState(""); const [pendingVerification, setPendingVerification] = useState(false); const [code, setCode] = useState(""); const router = useRouter(); // This function will handle the user submitting their email and password const handleSubmit = async (e) => { e.preventDefault(); if (!isLoaded) { return; } // Start the sign-up process using the email and password provided try { await signUp.create({ emailAddress, password, }); // Send the user an email with the verification code await signUp.prepareEmailAddressVerification({ strategy: "email_code" }); // Set 'verifying' true to display second form and capture the OTP code setPendingVerification(true); } catch (err: any) { // This can return an array of errors. // See https://clerk.com/docs/custom-flows/error-handling to learn about error handling console.error(JSON.stringify(err, null, 2)); } }; // This function will handle the user submitting a code for verification const onPressVerify = async (e) => { e.preventDefault(); if (!isLoaded) { return; } try { // Submit the code that the user provides to attempt verification const completeSignUp = await signUp.attemptEmailAddressVerification({ code, }); if (completeSignUp.status !== "complete") { // The status can also be `abandoned` or `missing_requirements` // Please see https://clerk.com/docs/references/react/use-sign-up#result-status for more information console.log(JSON.stringify(completeSignUp, null, 2)); } // Check the status to see if it is complete // If complete, the user has been created -- set the session active if (completeSignUp.status === "complete") { await setActive({ session: completeSignUp.createdSessionId }) // Redirect the user to a post sign-up route router.push("/"); } } catch (err: any) { // This can return an array of errors. // See https://clerk.com/docs/custom-flows/error-handling to learn about error handling console.error(JSON.stringify(err, null, 2)); } }; return ( <div> {!pendingVerification && ( <form> <div> <label htmlFor="email">Email</label> <input onChange={(e) => setEmailAddress(e.target.value)} id="email" name="email" type="email" /> </div> <div> <label htmlFor="password">Password</label> <input onChange={(e) => setPassword(e.target.value)} id="password" name="password" type="password" /> </div> <button onClick={handleSubmit}>Sign up</button> </form> )} {pendingVerification && ( <div> <form> <input value={code} placeholder="Code..." onChange={(e) => setCode(e.target.value)} /> <button onClick={onPressVerify}> Verify Email </button> </form> </div> )} </div> ); }

Create sign in flow

In email/password authentication, the sign-in is a process that requires users to provide their email address and their password and authenticates them by creating a new session for the user.

app/sign-in/[[...sign-in]].page.tsx
'use client' import * as React from "react"; import { useSignIn } from "@clerk/nextjs"; import { useRouter } from "next/navigation"; export default function SignInForm() { const { isLoaded, signIn, setActive } = useSignIn(); const [email, setEmail] = React.useState(""); const [password, setPassword] = React.useState(""); const router = useRouter(); // Handle the submission of the sign-in form const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!isLoaded) { return; } // Start the sign-in process using the email and password provided try { const completeSignIn = await signIn.create({ identifier: email, password, }); if (completeSignIn.status !== 'complete') { // The status can also be `needs_factor_on', 'needs_factor_two', or 'needs_identifier' // Please see https://clerk.com/docs/references/react/use-sign-in#result-status for more information console.log(JSON.stringify(completeSignIn, null, 2)); } if (completeSignIn.status === 'complete') { // If complete, user exists and provided password match -- set session active await setActive({ session: completeSignIn.createdSessionId }); // Redirect the user to a post sign-in route router.push('/'); } } catch (err: any) { // This can return an array of errors. // See https://clerk.com/docs/custom-flows/error-handling to learn about error handling console.error(JSON.stringify(err, null, 2)); } }; // Display a form to capture the user's email and password return ( <div> <form onSubmit={(e) => handleSubmit(e)}> <div> <label htmlFor="email">Email</label> <input onChange={(e) => setEmail(e.target.value)} id="email" name="email" type="email" value={email} /> </div> <div> <label htmlFor="password">Password</label> <input onChange={(e) => setPassword(e.target.value)} id="password" name="password" type="password" value={password} /> </div> <button type="submit">Sign In</button> </form> </div> ); }
pages/sign-in/[[...index]].tsx
import { useState } from "react"; import { useSignIn } from "@clerk/nextjs"; import { useRouter } from "next/router"; export default function SignInForm() { const { isLoaded, signIn, setActive } = useSignIn(); const [emailAddress, setEmailAddress] = useState(""); const [password, setPassword] = useState(""); const router = useRouter(); // Handle the submission of the sign-in form const handleSubmit = async (e) => { e.preventDefault(); if (!isLoaded) { return; } // Start the sign-in process using the email and password provided try { const result = await signIn.create({ identifier: emailAddress, password, }); if (result.status === "complete") { // If complete, user exists and provided password match -- set session active await setActive({ session: result.createdSessionId }); // Redirect the user to a post sign-in route router.push("/") } else { // The status can also be `needs_factor_on', 'needs_factor_two', or 'needs_identifier' // Please see https://clerk.com/docs/references/react/use-sign-in#result-status for more information console.error(JSON.stringify(results, null, 2)); } } catch (err: any) { // This can return an array of errors. // See https://clerk.com/docs/custom-flows/error-handling to learn about error handling console.error(JSON.stringify(err, null, 2)); } }; // Display a form to capture the user's email and password return ( <div> <form> <div> <label htmlFor="email">Email</label> <input onChange={(e) => setEmailAddress(e.target.value)} id="email" name="email" type="email" /> </div> <div> <label htmlFor="password">Password</label> <input onChange={(e) => setPassword(e.target.value)} id="password" name="password" type="password" /> </div> <button onClick={handleSubmit}>Sign In</button> </form> </div> ); }

Last updated on March 8, 2024

What did you think of this content?

Clerk © 2024