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.
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:
- Initiate the sign-up process by collecting the user's email address and password.
- Prepare the email address verification, which sends a one-time code to the given address.
- Attempt to complete the email address verification by supplying the one-time code.
- 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]].tsximport { 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> ); }
signup.tsximport { useState } from "react"; import { useSignUp } from "@clerk/clerk-react"; 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(""); // 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 }) } } 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/routes/sign-up/$.tsximport { useState } from "react"; import { useSignUp } from "@clerk/remix"; 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(""); // 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 }) // Handle your own logic here, like redirecting to a new page if needed. } } 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> ); }
sign-up.tsximport { useState } from "react"; // Use the react package when using Gatsby import { useSignUp } from "@clerk/react"; 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> ); }
SignUpScreen.tsximport * as React from "react"; import { Text, TextInput, TouchableOpacity, View } from "react-native"; import { useSignUp } from "@clerk/clerk-expo"; export default function SignUpScreen() { const { isLoaded, signUp, setActive } = useSignUp(); const [emailAddress, setEmailAddress] = React.useState(""); const [password, setPassword] = React.useState(""); const [pendingVerification, setPendingVerification] = React.useState(false); const [code, setCode] = React.useState(""); // start the sign up process. const onSignUpPress = async () => { 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 () => { if (!isLoaded) { return; } try { const completeSignUp = await signUp.attemptEmailAddressVerification({ code, }); await setActive({ session: completeSignUp.createdSessionId }); } catch (err: any) { console.error(JSON.stringify(err, null, 2)); } }; return ( <View> {!pendingVerification && ( <View> <View> <TextInput autoCapitalize="none" value={emailAddress} placeholder="Email..." onChangeText={(email) => setEmailAddress(email)} /> </View> <View> <TextInput value={password} placeholder="Password..." placeholderTextColor="#000" secureTextEntry={true} onChangeText={(password) => setPassword(password)} /> </View> <TouchableOpacity onPress={onSignUpPress}> <Text>Sign up</Text> </TouchableOpacity> </View> )} {pendingVerification && ( <View> <View> <TextInput value={code} placeholder="Code..." onChangeText={(code) => setCode(code)} /> </View> <TouchableOpacity onPress={onPressVerify}> <Text>Verify Email</Text> </TouchableOpacity> </View> )} </View> ); }
your-app.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Clerk JavaScript Email + Password</title> </head> <body> <h1>Clerk JavaScript Email + Password</h1> <div id="auth-signup"> <input placeholder="email" id="email" type="email"></input> <input placeholder="password" id="password" type="password"></input> <button onclick="SignUp()">SignUp</button> </div> <div id="auth-verification" hidden="true"> <input placeholder="code" id="code" type="text"></input> <button onclick="VerifyEmailAddress()">Verify</button> </div> <div id="user-button" /> <script> const SignUp = async () => { const emailAddress = document.getElementById('email').value; const password = document.getElementById('password').value; const {client} = window.Clerk; try { await client.signUp.create({ emailAddress, password }); await client.signUp.prepareEmailAddressVerification(); //hide signup form document.getElementById('auth-signup').hidden = true; //show verification form document.getElementById('auth-verification').hidden = false; } catch (err) { console.log(err) } }; const VerifyEmailAddress = async () => { const code = document.getElementById('code').value; const {client, setActive} = window.Clerk; try { // Verify the email address. const verify = await client.signUp.attemptEmailAddressVerification({ code }); // user is created now set the session to active this never is not null. await setActive({session: verify.createdSessionId}) } catch (err) { console.log(err) } } </script> // Script to load Clerk up <script src="src/script.js" async crossorigin="anonymous"></script> </body> </html>
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]].tsximport { 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> ); }
signin.tsximport { useState } from "react"; import { useSignIn } from "@clerk/clerk-react"; export default function SignInForm() { const { isLoaded, signIn, setActive } = useSignIn(); const [emailAddress, setEmailAddress] = useState(""); const [password, setPassword] = useState(""); // 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") { await setActive({ session: result.createdSessionId }); // redirect the user how you see fit. } 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/routes/sign-in/$.tsximport { useState } from "react"; import { useSignIn } from "@clerk/remix"; export default function SignInForm() { const { isLoaded, signIn, setActive } = useSignIn(); const [emailAddress, setEmailAddress] = useState(""); const [password, setPassword] = useState(""); // 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") { await setActive({ session: result.createdSessionId }); // redirect the user how you see fit. } 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> ); }
sign-in.tsximport { useState } from "react"; // Use React for Gatsby import { useSignIn } from "@clerk/clerk-react"; export default function SignInForm() { const { isLoaded, signIn, setActive } = useSignIn(); const [emailAddress, setEmailAddress] = useState(""); const [password, setPassword] = useState(""); // 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") { await setActive({ session: result.createdSessionId }); // redirect the user how you see fit. } 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> ); }
SignUpScreen.tsximport React from "react"; import { Text, TextInput, TouchableOpacity, View } from "react-native"; import { useSignIn } from "@clerk/clerk-expo"; export default function SignInScreen() { const { signIn, setActive, isLoaded } = useSignIn(); const [emailAddress, setEmailAddress] = React.useState(""); const [password, setPassword] = React.useState(""); const onSignInPress = async () => { if (!isLoaded) { return; } try { const completeSignIn = await signIn.create({ identifier: emailAddress, password, }); if (completeSignIn.status === "complete") { await setActive({ session: result.createdSessionId }); // redirect the user how you see fit. } else { /*Investigate why the login hasn't completed */ console.log(result); } } catch (err: any) { console.log(err); } }; return ( <View> <View> <TextInput autoCapitalize="none" value={emailAddress} placeholder="Email..." onChangeText={(emailAddress) => setEmailAddress(emailAddress)} /> </View> <View> <TextInput value={password} placeholder="Password..." secureTextEntry={true} onChangeText={(password) => setPassword(password)} /> </View> <TouchableOpacity onPress={onSignInPress}> <Text>Sign in</Text> </TouchableOpacity> </View> ); }
your-app.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Clerk JavaScript Email + Password</title> </head> <body> <h1>Clerk JavaScript Email + Password</h1> <div id="auth-signin"> <input placeholder="email" id="email" type="email"></input> <input placeholder="password" id="password" type="password"></input> <button onclick="SignIn()">SignIn</button> </div> <script> const SignIn = async () => { const emailAddress = document.getElementById('email').value; const password = document.getElementById('password').value; const {client} = window.Clerk; try { const signInAttempt = await client.signIn.create({ emailAddress, password }); if (signInAttempt.status === "complete") { await setActive({ session: signInAttempt.createdSessionId }); // redirect the user how you see fit. } else { /*Investigate why the login hasn't completed */ console.log(signInAttempt); } } catch (err) { console.log(err) } }; </script> // Script to load Clerk up <script src="src/script.js" async crossorigin="anonymous"></script> </body> </html>