Custom email password flow
In some cases, Clerk components may not work for your product which is why the useSignUp
and useSignIn
hooks are provided. These hooks allow you to build your own custom sign up and sign in flows.
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. You can do this by enabling this on the Email, Phone, and Username section.
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>