Forgot and Reset Password using better_auth, nextjs and resend
Daanish2003
Posted on November 28, 2024
In this blog we will dive into setting up forgot and reset password in a mordern web application. By leveraging a robust stack of tools and frameworks, you'll create a seamless and secure email verification system integrated into your full-stack application.
By the end of this guide, you'll have a functional system that allows user to create new password if they forgot their password
Tech Stack
Here’s what we’ll use:
- Better_Auth v1: A lightweight, extensible TypeScript authentication library.
- Next.js: A React framework for building server-rendered applications.
- Prisma: A modern ORM for efficient database interaction.
- Shadcn: A utility-first component library for rapid UI development.
- TailwindCSS: A popular utility-first CSS framework.
- Resend: A popular service for sending emails
Prerequisites
Before proceeding, ensure you have the following ready:
- Node.js (LTS version) installed.
- A package manager like npm, yarn, or pnpm (we'll use
pnpm
in this guide). - A PostgreSQL database instance (local or hosted, such as Supabase or PlanetScale).
- If you're working locally, Docker is a great way to set this up.
- Familiarity with TypeScript, Next.js, and Prisma.
Cloning the Starter Project:
Note: This project is continuation of EmailPassword auth and Email verification. If want to build from stratch please visit EmailPassword blog and Email verification blog or could just clone the branch of the repository
git clone -b feat-email-verification https://github.com/Daanish2003/better_auth_nextjs.git
Navigate into the folder and install all the dependencies
pnpm install
Add .env file
# General authentication settings
BETTER_AUTH_SECRET="your-secret-key" # Replace with your own secure key or generate one using "pnpm dlx auth secret"
BETTER_AUTH_URL="http://localhost:3000" # Base URL of your app during local development
NEXT_PUBLIC_APP_URL="http://localhost:3000" # Public-facing base URL of your app
#.env
POSTGRES_PASSWORD="your-password"
POSTGRES_USER="your-username"
DATABASE_URL="postgresql://your-username:your-password@localhost:5432/mydb?schema=public"
RESEND_API_KEY="your-resend-api-key"
Run the docker if you have docker locally
docker compose up -d
Step 1: Update auth.ts
file
Open src/lib/auth.ts
and configure sendResetPassword
export const auth = betterAuth({
// other options
emailAndPassword: {
enabled: true,
autoSignIn: true,
minPasswordLength: 8,
maxPasswordLength: 20,
requireEmailVerification: true,
// it sends the reset password token using resend to your email
sendResetPassword: async ({ user, url }) => {
await resend.emails.send({
from: "Acme <onboarding@resend.dev>",
to: user.email,
subject: "Reset your password",
html: `Click the link to reset your password: ${url}`,
});
},
},
// other options
})
Step 2: Create Schema for validation for forgot and reset password
Create a file named forgot-password-schema.tsx
inside the folder src/helpers/zod
paste the code inside the file
import { z } from "zod";
export const ForgotPasswordSchema = z.object({
email: z
.string() // string type
.email({message: "Invalid type"}) // checks if the input given by the user is email
.min(1, {message: "Email is required"}), // checks if the email field is empty or not
})
Then create another file named reset-password-schema.tsx
inside the same folder src/helpers/zod
import { z } from "zod";
export const ResetPasswordSchema = z.object({
password: z
.string() // check if it is string type
.min(8, { message: "Password must be at least 8 characters long" }) // checks for character length
.max(20, { message: "Password must be at most 20 characters long" }),
confirmPassword: z
.string()
.min(8, { message: "Password must be at least 8 characters long" })
.max(20, { message: "Password must be at most 20 characters long" }),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
// checks if the password and confirm password are equal
});
Step 3: Create UI components for forgot and reset password
Create a component named forgot-password.tsx
inside the component folder or any other location.
Paste the code inside of it
"use client"
import { useForm } from 'react-hook-form'
import CardWrapper from '../card-wrapper'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../ui/form'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { Input } from '../ui/input'
import { Button } from '../ui/button'
import FormError from '../form-error'
import { FormSuccess } from '../form-success'
import { useAuthState } from '@/hooks/useAuthState'
import { authClient } from '@/lib/auth-client'
import { ForgotPasswordSchema } from '@/helpers/zod/forgot-password-schema'
const ForgotPassword = () => {
const { error, success, loading, setError, setSuccess, setLoading, resetState } = useAuthState()
const form = useForm<z.infer<typeof ForgotPasswordSchema>>({
resolver: zodResolver(ForgotPasswordSchema),
defaultValues: {
email: '',
}
})
const onSubmit = async (values: z.infer<typeof ForgotPasswordSchema>) => {
try {
// Call the authClient's forgetPassword method, passing the email and a redirect URL.
await authClient.forgetPassword({
email: values.email, // Email to which the reset password link should be sent.
redirectTo: "/reset-password" // URL to redirect the user after resetting the password.
}, {
// Lifecycle hooks to handle different stages of the request.
onResponse: () => {
setLoading(false)
},
onRequest: () => {
resetState()
setLoading(true)
},
onSuccess: () => {
setSuccess("Reset password link has been sent")
},
onError: (ctx) => {
setError(ctx.error.message);
},
});
} catch (error) { // catch the error
console.log(error)
setError("Something went wrong")
}
}
return (
<CardWrapper
cardTitle='Forgot Password'
cardDescription='Enter your email to send link to reset password'
cardFooterDescription="Remember your password?"
cardFooterLink='/signin'
cardFooterLinkTitle='Signin'
>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
disabled={loading}
type="email"
placeholder='example@gmail.com'
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormError message={error} />
<FormSuccess message={success} />
<Button disabled={loading} type="submit" className='w-full'>Submit</Button>
</form>
</Form>
</CardWrapper>
)
}
export default ForgotPassword
Then Create an another component named reset-password.tsx
inside the component folder or any other location.
Paste the code inside of it
"use client"
import React from 'react'
import { Button } from '../ui/button'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../ui/form'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { Input } from '../ui/input'
import { useRouter } from 'next/navigation'
import CardWrapper from '../card-wrapper'
import FormError from '../form-error'
import { FormSuccess } from '../form-success'
import { ResetPasswordSchema } from '@/helpers/zod/reset-password-schema'
import { authClient } from '@/lib/auth-client'
import { useAuthState } from '@/hooks/useAuthState'
const ResetPassword = () => {
const router = useRouter()
const { error, success, loading, setError, setLoading, setSuccess, resetState } = useAuthState()
const form = useForm<z.infer<typeof ResetPasswordSchema>>({
resolver: zodResolver(ResetPasswordSchema),
defaultValues: {
password: '',
confirmPassword: ''
}
})
const onSubmit = async (values: z.infer<typeof ResetPasswordSchema>) => {
try {
// Call the authClient's reset password method, passing the email and a redirect URL.
await authClient.resetPassword({
newPassword: values.password, // new password given by user
}, {
onResponse: () => {
setLoading(false)
},
onRequest: () => {
resetState()
setLoading(true)
},
onSuccess: () => {
setSuccess("New password has been created")
router.replace('/signin')
},
onError: (ctx) => {
setError(ctx.error.message);
},
});
} catch (error) { // catches the error
console.log(error)
setError("Something went wrong")
}
}
return (
<CardWrapper
cardTitle='Reset Password'
cardDescription='create a new password'
cardFooterLink='/signin'
cardFooterDescription='Remember your password?'
cardFooterLinkTitle='Signin'
>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
disabled={loading}
type="password"
placeholder='************'
{...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="confirmPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Confirm Password</FormLabel>
<FormControl>
<Input
disabled={loading}
type="password"
placeholder='*************'
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormError message={error} />
<FormSuccess message={success} />
<Button type="submit" className="w-full" disabled={loading}>Submit</Button>
</form>
</Form>
</CardWrapper>
)
}
export default ResetPassword
Explanation:
-
UI and Form Utilities:
- Components like
CardWrapper
,Form
,Input
, andButton
are used to construct the form UI. -
FormError
andFormSuccess
display error or success messages.
- Components like
-
Form Handling:
-
useForm
fromreact-hook-form
handles form state and validation. -
zodResolver
integrates Zod schema validation with React Hook Form.
-
-
Authentication Utilities:
-
useAuthState
manages theerror
,success
, andloading
states for the authentication process. -
authClient
facilitates server-side password reset requests.
-
-
Schema:
-
ResetPasswordSchema
andForgotPasswordSchema
defines the expected shape of the form data (email or password) and validation rules using Zod.
-
Step 4: Create a page.tsx
file for both forgot-password
and reset-password
Create a folder named forgot-password and reset-password inside the src/app/(auth)/
and create page.tsx
file inside of it.
Import the Forgot Password and Reset Password component
// app/(auth)/forgot-password/page.tsx
import ForgotPassword from '@/components/auth/forgot-password'
import React from 'react'
const ForgotPasswordPage = () => {
return (
<ForgotPassword />
)
}
export default ForgotPasswordPage
// app/(auth)/reset-password/page.tsx
import ResetPassword from '@/components/auth/reset-password'
import React from 'react'
const ResetPasswordPage = () => {
return (
<ResetPassword />
)
}
export default ResetPasswordPage
Step 4: Update Signin
component
Open the sign-in.tsx
file and then update with following code
// components/auth/sign-in.tsx
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
disabled={loading}
type="password"
placeholder="********"
{...field}
>
</FormControl>
<FormMessage />
// Just add link below so user could redirect to forgot-password-url
<Link href="/forgot-password" className="text-xs underline ml-60">
Forgot Password?
</Link>
</FormItem>
)}
/>
Add the Link
component below the Form Message component
Step 5: Run Your Application
Start your development server:
pnpm dev
- Access your application at
http://localhost:3000/forgot-password
to test forgot-password. - Check your inbox for the reset-password link.
- Log in at
http://localhost:3000/signin
once new password is created.
Final Thoughts
Congratulations! You have successfully integrating Forgot Password and Reset Password into your application, providing users with a seamless and secure authentication experience. 🎉
If you enjoyed this blog, please consider following and supporting me! Your encouragement motivates me to create more helpful content for you. 😊
Reference Links:
Email Verification Blog: https://dev.to/daanish2003/email-verification-using-betterauth-nextjs-and-resend-37gn
Email And Password with Better_Auth: https://dev.to/daanish2003/email-and-password-auth-using-betterauth-nextjs-prisma-shadcn-and-tailwindcss-hgc
OAuth Blog: https://dev.to/daanish2003/oauth-using-betterauth-nextjs-prisma-shadcn-and-tailwindcss-45bp
Better_Auth Docs: https://www.better-auth.com/
pnpm Docs: https://pnpm.io/
Docker Docs: https://docs.docker.com/
Prisma Docs: https://www.prisma.io/docs/getting-started
Shadcn Docs: https://ui.shadcn.com/
Next.js Docs: https://nextjs.org/
Tailwindcss Docs: https://tailwindcss.com/
Github repository: https://github.com/Daanish2003/better_auth_nextjs
Posted on November 28, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 27, 2024