Skip to Content
Clerk logo

Clerk Docs

Ctrl + K
Go to clerkstage.dev

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. Navigate to the Email, Phone, and Username tab of the User & Authentication section in the Clerk Dashboard. 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.
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(); // start the sign up process. const handleSubmit = async (e) => { e.preventDefault(); if (!isLoaded) { return; } try { await signUp.create({ emailAddress, password, }); // send the email. await signUp.prepareEmailAddressVerification({ strategy: "email_code" }); // change the UI to our pending section. setPendingVerification(true); } catch (err: any) { console.error(JSON.stringify(err, null, 2)); } }; // This verifies the user using email code that is delivered. const onPressVerify = async (e) => { e.preventDefault(); if (!isLoaded) { return; } try { const completeSignUp = await signUp.attemptEmailAddressVerification({ code, }); if (completeSignUp.status !== "complete") { /* investigate the response, to see if there was an error or if the user needs to complete more steps.*/ console.log(JSON.stringify(completeSignUp, null, 2)); } if (completeSignUp.status === "complete") { await setActive({ session: completeSignUp.createdSessionId }) router.push("/"); } } catch (err: any) { 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> ); }
app/sign-up/[[...sign-up]].page.tsx
"use client" import { useState } from "react"; import { useSignUp } from "@clerk/nextjs"; import { useRouter } from "next/navigation"; 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(); // start the sign up process. const handleSubmit = async (e) => { e.preventDefault(); if (!isLoaded) { return; } try { await signUp.create({ emailAddress, password, }); // send the email. await signUp.prepareEmailAddressVerification({ strategy: "email_code" }); // change the UI to our pending section. setPendingVerification(true); } catch (err: any) { console.error(JSON.stringify(err, null, 2)); } }; // This verifies the user using email code that is delivered. const onPressVerify = async (e) => { e.preventDefault(); if (!isLoaded) { return; } try { const completeSignUp = await signUp.attemptEmailAddressVerification({ code, }); if (completeSignUp.status !== "complete") { /* investigate the response, to see if there was an error or if the user needs to complete more steps.*/ console.log(JSON.stringify(completeSignUp, null, 2)); } if (completeSignUp.status === "complete") { await setActive({ session: completeSignUp.createdSessionId }) router.push("/"); } } catch (err: any) { 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.

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(); // start the sign In process. const handleSubmit = async (e) => { e.preventDefault(); if (!isLoaded) { return; } try { const result = await signIn.create({ identifier: emailAddress, password, }); if (result.status === "complete") { console.log(result); await setActive({ session: result.createdSessionId }); router.push("/") } else { /*Investigate why the login hasn't completed */ console.log(result); } } catch (err: any) { console.error("error", err.errors[0].longMessage) } }; 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> ); }
app/sign-in/[[...sign-in]].page.tsx
"use client" import { useState } from "react"; import { useSignIn } from "@clerk/nextjs"; import { useRouter } from "next/navigation"; export default function SignInForm() { const { isLoaded, signIn, setActive } = useSignIn(); const [emailAddress, setEmailAddress] = useState(""); const [password, setPassword] = useState(""); const router = useRouter(); // start the sign In process. const handleSubmit = async (e) => { e.preventDefault(); if (!isLoaded) { return; } try { const result = await signIn.create({ identifier: emailAddress, password, }); if (result.status === "complete") { console.log(result); await setActive({ session: result.createdSessionId }); router.push("/") } else { /*Investigate why the login hasn't completed */ console.log(result); } } catch (err: any) { console.error("error", err.errors[0].longMessage) } }; 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> ); }

What did you think of this content?

Clerk © 2023