Skip to Content
You are viewing a beta version of Clerk Docs
Visit the latest docs
Clerk logo

Clerk Docs

Ctrl + K
Go to clerk.com

Create a custom Forgot Password flow using Clerk's API

Clerk's prebuilt components provide a Forgot Password flow for your users out of the box. However, if you want more control over the user experience, you can create a custom Forgot Password flow using the lower-level methods provided by the ClerkJS SDK.

In the following example, the user is asked to provide their email address. After submitting their email, the user is asked to provide a new password and the password reset code that was sent to their email. The user is then signed in with their new password.

Note that this example's user interface does not handle 2FA (two-factor authentication). If it detects that 2FA is needed for the account trying to reset the password, it will display a message to the user that says "2FA is required, but this UI does not handle that".

For the sake of this guide, this example is written for Next.js App Router but it is supported by any React meta framework, such as Remix or Gatsby.

app/forgot-password.tsx
"use client" import React, { useState } from 'react'; import { useAuth, useSignIn } from '@clerk/nextjs'; import type { NextPage } from 'next'; import { useRouter } from 'next/navigation'; const ForgotPasswordPage: NextPage = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [code, setCode] = useState(''); const [successfulCreation, setSuccessfulCreation] = useState(false); const [secondFactor, setSecondFactor] = useState(false); const [error, setError] = useState(''); const router = useRouter(); const { isSignedIn } = useAuth(); const { isLoaded, signIn, setActive } = useSignIn(); if (!isLoaded) { return null; } // If the user is already signed in, // redirect them to the home page if (isSignedIn) { router.push('/'); } // Send the password reset code to the user's email async function create(e: React.FormEvent) { e.preventDefault(); await signIn ?.create({ strategy: 'reset_password_email_code', identifier: email, }) .then(_ => { setSuccessfulCreation(true); setError(''); }) .catch(err => { console.error('error', err.errors[0].longMessage); setError(err.errors[0].longMessage); }); } // Reset the user's password. // Upon successful reset, the user will be // signed in and redirected to the home page async function reset(e: React.FormEvent) { e.preventDefault(); await signIn ?.attemptFirstFactor({ strategy: 'reset_password_email_code', code, password, }) .then(result => { // Check if 2FA is required if (result.status === 'needs_second_factor') { setSecondFactor(true); setError(''); } else if (result.status === 'complete') { // Set the active session to // the newly created session (user is now signed in) setActive({ session: result.createdSessionId }); setError(''); } else { console.log(result); } }) .catch(err => { console.error('error', err.errors[0].longMessage) setError(err.errors[0].longMessage); }); } return ( <div style={{ margin: 'auto', maxWidth: '500px', }} > <h1>Forgot Password?</h1> <form style={{ display: 'flex', flexDirection: 'column', gap: '1em', }} onSubmit={!successfulCreation ? create : reset} > {!successfulCreation && ( <> <label htmlFor='email'>Please provide your email address</label> <input type='email' placeholder='e.g john@doe.com' value={email} onChange={e => setEmail(e.target.value)} /> <button>Send password reset code</button> {error && <p>{error}</p>} </> )} {successfulCreation && ( <> <label htmlFor='password'>Enter your new password</label> <input type='password' value={password} onChange={e => setPassword(e.target.value)} /> <label htmlFor='password'>Enter the password reset code that was sent to your email</label> <input type='text' value={code} onChange={e => setCode(e.target.value)} /> <button>Reset</button> {error && <p>{error}</p>} </> )} {secondFactor && <p>2FA is required, but this UI does not handle that</p>} </form> </div> ); }; export default ForgotPasswordPage;

Prompting users to reset compromised passwords during sign-in

If you have enabled rejection of compromised passwords also on sign-in, then it is possible for the sign-in attempt to be rejected with the form_password_pwned error code.

In this case, you can prompt the user to reset their password using the exact same logic detailed in the previous section.

Last updated on April 3, 2024

What did you think of this content?

Clerk © 2024