Lädt...


🔧 Email verification using better_auth, nextjs and resend


Nachrichtenbereich: 🔧 Programmierung
🔗 Quelle: dev.to

In this blog, we'll dive into setting up email verification in a modern 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 validates users via email before granting access to their accounts.

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.

Prerequisites

Before proceeding, ensure you have the following ready:

  1. Node.js (LTS version) installed.
  2. A package manager like npm, yarn, or pnpm (we'll use pnpm in this guide).
  3. 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.
  4. Familiarity with TypeScript, Next.js, and Prisma.

Note:

This blog is the continuation of Email and Password Auth If you want to follow along please first visit: Email and Password Auth using Better_Auth or you could clone the branch of the repository from the command below to follow along.

Clone Repo:

git clone -b feat-email-password-auth https://github.com/Daanish2003/better_auth_nextjs.git

Install Dependencies:

pnpm install

Run the docker if you have docker locally:

docker compose up -d

Step 1: Setting up Resend service

Resend is a service for sending transactional emails like password resets and email verifications.

Install the Resend SDK

Add the Resend package to your project:

pnpm add resend

Get Your Resend API Key

  1. Visit Resend.
  2. Log in or create an account.
  3. Navigate to API Keys and create a new key.
  4. Add the API key to your .env file:
RESEND_API_KEY=your-api-key-here

Create a Helper for Resend

Organize your code by creating a reusable helper for Resend.

  1. Create a folder src/helpers/email/.
  2. Add a file named resend.ts:
import { Resend } from "resend";

export const resend = new Resend(process.env.RESEND_API_KEY);

Step 2: Integrate Email verification with Better_Auth

Better_Auth provides native support for email verification.

Update the auth.ts File

Open src/lib/auth.ts and configure email verification:

// auth.ts
import prisma from "@/db";
import { resend } from "@/helpers/email/resend";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";

export const auth = betterAuth({
  appName: "better_auth_nextjs",
  database: prismaAdapter(prisma, {
    provider: "postgresql",
  }),
  emailAndPassword: {
    enabled: true,
    autoSignIn: true,
    minPasswordLength: 8,
    maxPasswordLength: 20,
    requireEmailVerification: true, //It does not allow user to login without email verification [!code highlight]
  },
emailVerification: {
    sendOnSignUp: true, // Automatically sends a verification email at signup
    autoSignInAfterVerification: true, // Automatically signIn the user after verification
    sendVerificationEmail: async ({ user, url }) => {
      await resend.emails.send({
        from: "Acme <[email protected]>", // You could add your custom domain
        to: user.email, // email of the user to want to end
        subject: "Email Verification", // Main subject of the email
        html: `Click the link to verify your email: ${url}`, // Content of the email
        // you could also use "React:" option for sending the email template and there content to user
      });
    },
  },
// ignore this if your not adding OAuth
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      redirectURI: process.env.BETTER_AUTH_URL + "/api/auth/callback/google",
    },
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
      redirectURI: process.env.BETTER_AUTH_URL + "/api/auth/callback/github",
    },
  },
});

Explanation:

  • sendVerificationEmail: This function is triggered when email verification starts. It accepts a data object with the following properties:
    • user: The user object containing the email address.
    • url: The verification URL the user must click to verify their email.

Step 3: Update UI Components

To handle the email verification workflow, we’ll update the sign-in and sign-up components.

Update the SignIn Component

Navigate to src/components/auth/sign-in.tsx and use this code:

// components/auth/sign-in.tsx
"use client"
import React from 'react'
import CardWrapper from '../card-wrapper'
import FormError from '../form-error'
import { FormSuccess } from '../form-success'
import { FcGoogle } from 'react-icons/fc'
import SocialButton from './social-button'
import { FaGithub } from 'react-icons/fa'
import { useAuthState } from '@/hooks/useAuthState'
import LoginSchema from '@/helpers/zod/login-schema'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../ui/form'
import { Input } from '../ui/input'
import { Button } from '../ui/button'
import { signIn } from '@/lib/auth-client'
import { useRouter } from 'next/navigation'

const SignIn = () => {
    const router = useRouter()
    const { error, success, loading, setSuccess, setError, setLoading, resetState } = useAuthState();

    const form = useForm<z.infer<typeof LoginSchema>>({
        resolver: zodResolver(LoginSchema),
        defaultValues: {
            email: '',
            password: '',
        }
    })

    const onSubmit = async (values: z.infer<typeof LoginSchema>) => {
        try {
          await signIn.email({
            email: values.email,
            password: values.password
          }, {
            onResponse: () => {
              setLoading(false)
            },
            onRequest: () => {
              resetState()
              setLoading(true)
            },
            onSuccess: () => {
                setSuccess("LoggedIn successfully")
                router.replace('/')
            },
// changes were made on onError option
            onError: (ctx) => {
                /* Whenever user tried to signin but email is not verified it catches the error and display the error */
                if(ctx.error.status === 403) {
                    setError("Please verify your email address")
                }
                /* handles other error */
              setError(ctx.error.message);
            },
          });
        } catch (error) {
          console.log(error)
          setError("Something went wrong")
        }
      }

    return (
        <CardWrapper
            cardTitle='Sign In'
            cardDescription='Enter your email below to login to your account'
            cardFooterDescription="Don't have an account?"
            cardFooterLink='/signup'
            cardFooterLinkTitle='Sign up'
        >
            <Form {...form}>
                <form className='space-y-4' onSubmit={form.handleSubmit(onSubmit)}>
                    <FormField
                        control={form.control}
                        name="email"
                        render={({ field }) => (
                            <FormItem>
                                <FormLabel>Email</FormLabel>
                                <FormControl>
                                    <Input
                                        disabled={loading}
                                        type="email"
                                        placeholder='[email protected]'
                                        {...field}
                                    />
                                </FormControl>
                                <FormMessage />
                            </FormItem>
                        )}
                    />
                    <FormField
                        control={form.control}
                        name="password"
                        render={({ field }) => (
                            <FormItem>
                                <FormLabel>Password</FormLabel>
                                <FormControl>
                                    <Input
                                        disabled={loading}
                                        type="password"
                                        placeholder='********'
                                        {...field}
                                    />
                                </FormControl>
                                <FormMessage />
                            </FormItem>

                        )}
                    />
                    <FormError message={error} />
                    <FormSuccess message={success} />
                     // ignore this if your not adding OAuth into your app
                    <Button disabled={loading} type="submit" className='w-full'>Login</Button>
                    <div className='flex gap-x-2'>
                        <SocialButton provider="google" icon={<FcGoogle />} label="Sign in with Google" />
                        <SocialButton provider="github" icon={<FaGithub />} label="Sign in with GitHub" />
                    </div>
                </form>
            </Form>
        </CardWrapper>
    )
}

export default SignIn

Update the SignUp Component

Similarly, update src/components/auth/sign-up.tsx:

// components/auth/sign-up.tsx
"use client"
import React from 'react'
import CardWrapper from '../card-wrapper'
import FormError from '../form-error'
import { FormSuccess } from '../form-success'
import { FcGoogle } from 'react-icons/fc'
import SocialButton from './social-button'
import { FaGithub } from 'react-icons/fa'
import { useAuthState } from '@/hooks/useAuthState'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../ui/form'
import { Input } from '../ui/input'
import { Button } from '../ui/button'
import { SignupSchema } from '@/helpers/zod/signup-schema'
import { signUp } from '@/lib/auth-client'

const SignUp = () => {
    const { error, success, loading, setLoading, setError, setSuccess, resetState } = useAuthState();

    const form = useForm<z.infer<typeof SignupSchema>>({
        resolver: zodResolver(SignupSchema),
        defaultValues: {
            name: '',
            email: '',
            password: '',
        }
    })

    const onSubmit = async (values: z.infer<typeof SignupSchema>) => {
        try {
            await signUp.email({
                name: values.name,
                email: values.email,
                password: values.password,
                callbackURL:'/' // redirect the user after email is verified
            }, {
                onResponse: () => {
                    setLoading(false)
                },
                onRequest: () => {
                    resetState()
                    setLoading(true)
                },
                onSuccess: () => {
                    // setSuccess("User has been created")
                    // router.replace('/')
                    setSuccess("Verification link has been sent to your mail")
                },
                onError: (ctx) => {
                    setError(ctx.error.message);
                },
            });
        } catch (error) {
            console.error(error)
            setError("Something went wrong")
        }

    }

    return (
        <CardWrapper
        cardTitle='SignUp'
        cardDescription='Create an new account'
        cardFooterLink='/login'
        cardFooterDescription='Already have an account?'
        cardFooterLinkTitle='Login'
        >
            <Form {...form}>
                <form className='space-y-4' onSubmit={form.handleSubmit(onSubmit)}>
                <FormField
                        control={form.control}
                        name="name"
                        render={({ field }) => (
                            <FormItem>
                                <FormLabel>Name</FormLabel>
                                <FormControl>
                                    <Input
                                        disabled={loading}
                                        type="text"
                                        placeholder='john'
                                        {...field} />
                                </FormControl>
                                <FormMessage />
                            </FormItem>
                        )}
                    />
                    <FormField
                        control={form.control}
                        name="email"
                        render={({ field }) => (
                            <FormItem>
                                <FormLabel>Email</FormLabel>
                                <FormControl>
                                    <Input
                                        disabled={loading}
                                        type="email"
                                        placeholder='[email protected]'
                                        {...field}
                                    />
                                </FormControl>
                                <FormMessage />
                            </FormItem>
                        )}
                    />
                    <FormField
                        control={form.control}
                        name="password"
                        render={({ field }) => (
                            <FormItem>
                                <FormLabel>Password</FormLabel>
                                <FormControl>
                                    <Input
                                        disabled={loading}
                                        type="password"
                                        placeholder='********'
                                        {...field}
                                    />
                                </FormControl>
                                <FormMessage />
                            </FormItem>

                        )}
                    />
                    <FormError message={error} />
                    <FormSuccess message={success} />
                     //ignore this if your not adding Oauth
                    <Button disabled={loading} type="submit" className='w-full'>Submit</Button>
                    <div className='flex gap-x-2'>
                        <SocialButton provider="google" icon={<FcGoogle />} label="Sign in with Google" />
                        <SocialButton provider="github" icon={<FaGithub />} label="Sign in with GitHub" />
                    </div>
                </form>
            </Form>
        </CardWrapper>
    )
}

export default SignUp

Step 4: Run Your Application

Start your development server:

pnpm dev

  • Access your application at http://localhost:3000/signup to test sign-up.
  • Check your inbox for the verification email.
  • Log in at http://localhost:3000/signin once verified.

Image description

Final Thoughts

Congratulations! You have successfully integrated Email verification 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 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

...

🔧 Email verification using better_auth, nextjs and resend


📈 84.26 Punkte
🔧 Programmierung

🔧 Forgot and Reset Password using better_auth, nextjs and resend


📈 69.26 Punkte
🔧 Programmierung

🔧 Two-Factor Authentication Using Better_Auth, Next.js, Prisma, ShadCN, and Resend


📈 54.99 Punkte
🔧 Programmierung

🔧 Magic link authentication using Better_Auth, Next.js, Shadcn, Prisma Resend, Tailwindcss


📈 53.74 Punkte
🔧 Programmierung

🔧 Email and Password auth using Better_Auth, nextjs, prisma, shadcn and tailwindcss


📈 52.93 Punkte
🔧 Programmierung

🔧 OAuth using Better_Auth, nextjs, prisma, shadcn and tailwindcss


📈 45.65 Punkte
🔧 Programmierung

🔧 Anonymous login using better_auth, nextjs, prisma, shadcn


📈 44.39 Punkte
🔧 Programmierung

🔧 Google oneTap login using Better_Auth, nextjs, prisma, shadcn


📈 44.39 Punkte
🔧 Programmierung

🔧 How to Create and Send Email Templates Using React Email and Resend in Next.js


📈 40.87 Punkte
🔧 Programmierung

🔧 How to Create Dynamic Email Contact Form in Next.js Using Resend and Zod


📈 33.59 Punkte
🔧 Programmierung

🔧 How to send emails using Resend and React Email ?


📈 33.59 Punkte
🔧 Programmierung

🔧 How to send emails using Resend and React Email ?


📈 33.59 Punkte
🔧 Programmierung

🔧 Username and Password Authentication with Better_Auth, Next.js, Prisma, Shadcn, and TailwindCSS


📈 29.94 Punkte
🔧 Programmierung

🔧 Building background email notifications with Next.js, Resend and Trigger.dev


📈 29.65 Punkte
🔧 Programmierung

🔧 Custom Emails with Supertokens, Resend, and React Email


📈 29.65 Punkte
🔧 Programmierung

🕵️ Reddit: Able to bypass email verification and change email to any other user email


📈 29.55 Punkte
🕵️ Sicherheitslücken

🔧 Top Hacker News Story Aggregator Using Next.js, Resend and Cron Atlas


📈 27.56 Punkte
🔧 Programmierung

🔧 What is NextJs?  Why Should You Use it in 2023?  NextJs Pros and Cons Guide


📈 27.29 Punkte
🔧 Programmierung

🔧 NextJS examples: 60 popular websites built with NextJS and headless CMS


📈 27.29 Punkte
🔧 Programmierung

🔧 3 Exciting Improvements Between NextJS 14 And NextJS 13


📈 27.29 Punkte
🔧 Programmierung

🔧 How to Set up Email Verification in PHP via a Verification Token: Complete Guide


📈 26.46 Punkte
🔧 Programmierung

🔧 How to Send Emails with React Using Resend


📈 26.31 Punkte
🔧 Programmierung

🔧 How to Build an Invoice SaaS App with Next.js, Resend, Clerk and Neon Postgres


📈 23.62 Punkte
🔧 Programmierung

🔧 Tech Update: Next.js, Resend, and Cron Atlas Power Latest Hacker News Story Aggregator


📈 23.62 Punkte
🔧 Programmierung

🔧 Simple Next.js Magic Link JWT Authentication with Prisma, PostgreSQL, and Resend


📈 23.62 Punkte
🔧 Programmierung

🕵️ Stripo Inc: Bypass email verification and create email template with the editor


📈 23.53 Punkte
🕵️ Sicherheitslücken

🕵️ Stripo Inc: my.stripo.emai email verification bypassed and also create email templates


📈 23.53 Punkte
🕵️ Sicherheitslücken

🐧 [PATCH resend] fat: Improve the readahead for FAT entries


📈 22.37 Punkte
🐧 Linux Tipps

🔧 Learn Resend in 2 hours — Full course 4k 2023


📈 22.37 Punkte
🔧 Programmierung

🔧 Learn Resend in 2 hours — Full course 4k 2023


📈 22.37 Punkte
🔧 Programmierung

🔧 Resend: Transformando a Forma Como Enviamos E-mails Transacionais


📈 22.37 Punkte
🔧 Programmierung

matomo