Как отправить подтверждение по электронной почте в Next.js 14 с помощью NextAuth.js, повторной отправки и ответа на электронную почту

Как отправить подтверждение по электронной почте в Next.js 14 с помощью NextAuth.js, повторной отправки и ответа на электронную почту

3 апреля 2024 г.

Изучив, как реализовать систему аутентификации в Next.js 14 с помощью NextAuth.js(Auth.js) в первой части этого блога, крайне важно сделать следующий шаг, чтобы обеспечить достоверность информации пользователя: электронную почту проверка.

Этот процесс является не только дополнительным шагом в обеспечении безопасности нашего приложения, но и важным компонентом, обеспечивающим легитимность и безопасность взаимодействия между пользователем и платформой.

Во второй части мы сосредоточимся на интеграции проверки электронной почты путем отправки электронных писем, использовании Resend для отправки электронных писем и React Email для создания привлекательных и функциональных шаблонов электронной почты.

Первоначальная конфигурация

Убедитесь, что в вашем проекте уже реализована система аутентификации, описанная в первой части блога. Это включает в себя правильную настройку Next.js 14 и NextAuth.

https://hackernoon.com/how-to-implement-authentication-in-nextjs-14-with-nextauthjs-shadcnui-react-hook-form-and-zod?embedable=true

Повторная отправка и интеграция электронной почты Ract в Next.js

Настройка

  1. Установите зависимости, необходимые в проекте. На этот раз мы будем использовать pnpm, вы можете использовать менеджер пакетов по вашему выбору.
  2. pnpm add resend react-email @react-email/components
    

    * Отправить повторно: для отправки электронных писем.

    * React Email: для создания красивых электронных писем с помощью React и TypeScript.

    2. Создайте следующую структуру проекта:

    ...
    ├── emails/
    │   └── verification-template.tsx
    ...
    ├── src/
    │   ├── actions/
    │   │   ├── email-actions.tsx
    │   │   └── auth-actions.tsx
    │   ├── app/
    │   │   ...
    │   │   ├── (primary)/
    │   │   │   ├── auth/
    │   │   │   │   └── verify-email/
    │   │   │   │       └── page.tsx
    │   │   │   ├── layout.tsx
    │   │   │   └── page.tsx
    │   │   │   ...   
    │   ├── components/
    │   │   └── auth/
    │   │       ├── signin-form.tsx
    │   │       ├── signup-form.tsx
    │   │       ...
    │   ...
    │   ├── utils.ts
    │   ...
    ...
    ├── .env
    ...
    

    Создание шаблонов электронной почты с помощью React Email

    React Email позволяет создавать шаблоны электронных писем с использованием JSX, что облегчает создание привлекательных и согласованных электронных писем с вашим брендом.

    Давайте создадим базовый шаблон электронной почты в виде компонента React. В этом случае мы создадим шаблон, который будет отправлен пользователю для подтверждения адреса электронной почты.

    emails/verification-template.tsx :

    // Import React and necessary components from @react-email/components
    import * as React from 'react';
    import { Body, Button, Container, Head, Hr, Html, Img, Preview, Section, Text } from '@react-email/components';
    
    import { getBaseUrl } from '@/utils';
    
    // Obtain the base URL using the imported function
    const baseUrl = getBaseUrl();
    
    // Define the properties expected by the VerificationTemplate component
    interface VerificationTemplateProps {
        username: string;
        emailVerificationToken: string;
    }
    
    // Define the VerificationTemplate component that takes the defined properties
    export const VerificationTemplate = ({ username, emailVerificationToken }: VerificationTemplateProps) => (
        <Html>
            <Head />
            <Preview>Preview text that appears in the email client before opening the email.</Preview>
            <Body style={main}>
                <Container style={container}>
                    <Img
                        src='my-logo.png'
                        alt='My SaaS'
                        style={logo}
                    />
                    <Text style={title}>Hi {username}!</Text>
                    <Text style={title}>Welcome to Starter Kit for building a SaaS</Text>
                    <Text style={paragraph}>Please verify your email, with the link below:</Text>
                    <Section style={btnContainer}>
                        {/* Button that takes the user to the verification link */}
                        <Button
                            style={button}
                            href={`${baseUrl}/auth/verify-email?token=${emailVerificationToken}`}
                        >
                            Click here to verify
                        </Button>
                    </Section>
                    <Hr style={hr} />
                    <Text style={footer}>Something in the footer.</Text>
                </Container>
            </Body>
        </Html>
    );
    
    // Styles applied to different parts of the email for customization
    const main = {
        backgroundColor: '#020817',
        color: '#ffffff',
        fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif',
    };
    
    const container = {
        margin: '0 auto',
        padding: '20px 0 48px',
    };
    
    ...
    

    Этот компонент создает шаблон электронной почты в формате HTML, включающий стили и динамический контент.

    Свойства определены для получения username и emailVerificationToken. Эти свойства используются для настройки электронной почты пользователя и создания ссылки для подтверждения.

    Для проверки и тестирования шаблона React Email предоставляет команду для локального запуска сервера, который будет предоставлять созданные нами шаблоны внутри папки электронной почты.

    1. Мы создаем скрипт в package.json для запуска сервера.
    2. {
        "scripts": {
          "dev": "email dev"
        }
      }
      

      2. Выполняем скрипт, и сервер запускается на localhost; мы увидим экран, подобный следующему, со всеми созданными шаблонами.

      Local React Email

      В нашем случае у нас есть только один шаблон. Как вы можете видеть ниже, у нас есть предварительный просмотр электронного письма, которое будет отправлено пользователю.

      Email verification

      Отправка электронных писем с помощью повторной отправки

      1. Создайте аккаунт для повторной отправки.
      2. Создайте новый API KEY.
      3. Добавьте API KEY в файл .env.
      4. ...
        # resend
        RESEND_API_KEY="re_jYiFaXXXXXXXXXXXXXXXXXXX"
        

        4. Чтобы создать функциональность отправки электронных писем, мы могли бы создать конечные точки внутри папки api/ и выполнять запросы http, однако в данном случае мы сделаем это, воспользовавшись преимуществом потенциала действий сервера.

        actions/email-actions.ts :

        'use server'
        
        import React from 'react'
        import { Resend } from 'resend'
        
        // Creates an instance of Resend using the API KEY
        const resend = new Resend(process.env.RESEND_API_KEY)
        
        // Defines the data structure for an email.
        interface Email {
            to: string[] // An array of email addresses to which to send the email.
            subject: string // The subject of the email.
            react: React.ReactElement // The body of the email as a React element.
        }
        
        export const sendEmail = async (payload: Email) => {
            const { error } = await resend.emails.send({
                from: 'My SaaS <onboarding@resend.dev>', // Defines the sender's address.
                ...payload, // Expands the contents of 'payload' to include 'to', 'subject', and 'react'.
            })
        
            if (error) {
                console.error('Error sending email', error)
                return null
            }
        
            console.log('Email sent successfully')
            return true
        }
        

        Примечание. Чтобы протестировать бесплатную разработку, вам необходимо использовать в качестве отправителя адрес электронной почты «onboarding@resend.dev», в противном случае вам придется добавить собственный домен.

        5. Отправьте электронное письмо при регистрации нового пользователя.

        actions/auth-actions.ts :

        ...
        
        import { sendEmail } from './email-actions'
        
        import VerificationTemplate from '../../emails/verification-template'
        
        // Import a utility function to generate a secure token.
        import { generateSecureToken } from '@/utils'
        
        export async function registerUser(user: Partial<User>) {
         try {
          // Creates a new user in the database with the provided data.
          // Passwords are hashed using bcrypt for security.
          const createdUser = await prisma.user.create({
           data: {
            ...user,
            password: await bcrypt.hash(user.password as string, 10),
           } as User,
          })
        
          // Generates a secure token to be used for email verification.
          const emailVerificationToken = generateSecureToken()
        
          // Updates the newly created user with the email verification token.
          await prisma.user.update({
           where: {
            id: createdUser.id,
           },
           data: {
            emailVerificationToken,
           },
          })
        
          // Sends a verification email to the new user using the sendEmail function.
          await sendEmail({
           to: ['your Resend registered email', createdUser.email],
           subject: 'Verify your email address',
           react: React.createElement(VerificationTemplate, { username: createdUser.username, emailVerificationToken }),
          })
        
          return createdUser
         } catch (error) {
          console.log(error)
          if (error instanceof Prisma.PrismaClientKnownRequestError) {
           if (error.code === 'P2002') {
            // Returns a custom error message if the email already exists in the database.
            return { error: 'Email already exists.' }
           }
          }
        
          return { error: 'An unexpected error occurred.' }
         }
        }
        

        После создания пользователя и создания токена подтверждения функция отправляет электронное письмо новому пользователю.

        Это электронное письмо создано с использованием компонента React VerificationTemplate, персонализированного с использованием имени пользователя и токена подтверждения. Этот шаг имеет решающее значение для проверки того, что адрес электронной почты пользователя действителен и контролируется пользователем.

        Successfully signed up

        Страница для проверки токена

        После того как электронное письмо будет отправлено пользователю, оно будет содержать ссылку, которая вернет его обратно на сайт. Чтобы проверить электронную почту, нам нужно создать страницу.

        (основной)/auth/verify-email/page.tsx :

        /* 
          All imports
        */
        
        // Defines the prop types for the VerifyEmailPage component.
        interface VerifyEmailPageProps {
         searchParams: { [key: string]: string | string[] | undefined }
        }
        
        export default async function VerifyEmailPage({ searchParams }: VerifyEmailPageProps) {
         let message = 'Verifying email...'
         let verified = false 
         if (searchParams.token) { // Checks if a verification token is provided in the URL.
          // Attempts to find a user in the database with the provided email verification token.
          const user = await prisma.user.findUnique({
           where: {
            emailVerificationToken: searchParams.token as string,
           },
          })
        
          // Conditionally updates the message and verified status based on the user lookup.
          if (!user) {
           message = 'User not found. Check your email for the verification link.'
          } else {
           // If the user is found, updates the user record to mark the email as verified.
           await prisma.user.update({
            where: {
             emailVerificationToken: searchParams.token as string,
            },
            data: {
             emailVerified: true,
             emailVerificationToken: null, // Clears the verification token.
            },
           })
        
           message = `Email verified! ${user.email}`
           verified = true // Sets the verified status to true.
          }
         } else {
          // Updates the message if no verification token is found.
          message = 'No email verification token found. Check your email.'
         }
        
         return (
          <div className='grid place-content-center py-40'>
           <Card className='max-w-sm text-center'>
            <CardHeader>
             <CardTitle>Email Verification</CardTitle>
            </CardHeader>
            <CardContent>
             <div className='w-full grid place-content-center py-4'>
              {verified ? <EmailCheckIcon size={56} /> : <EmailWarningIcon size={56} />}
             </div>
             <p className='text-lg text-muted-foreground' style={{ textWrap: 'balance' }}>
              {message}
             </p>
            </CardContent>
            <CardFooter>
             {verified && (
              // Displays a sign-in link if the email is successfully verified.
              <Link href={'/auth/signin'} className='bg-primary text-white text-sm font-medium hover:bg-primary/90 h-10 px-4 py-2 rounded-lg w-full text-center'>
               Sign in
              </Link>
             )}
            </CardFooter>
           </Card>
          </div>
         )
        }
        

        После успешной проверки электронной почты пользователя мы увидим следующее сообщение.

        Page to validate the token

        Теперь мы реализуем последнюю проверку, когда пользователь хочет войти в систему, но еще не подтвердил свой адрес электронной почты.

        компоненты/auth/sigin-form.tsx :

        ...
        
         async function onSubmit(values: InputType) {
          try {
           setIsLoading(true)
        
           const response = await signIn('credentials', {
            redirect: false,
            email: values.email,
            password: values.password,
           })
           if (!response?.ok) {
        // if the email is not verified we will show a message to the user.
            if (response?.error === 'EmailNotVerified') {
             toast({
              title: 'Please, verify your email first.',
              variant: 'warning',
             })
        
             return
            }
        
            toast({
             title: 'Something went wrong!',
             description: response?.error,
             variant: 'destructive',
            })
            return
           }
        
           toast({
            title: 'Welcome back! ',
            description: 'Redirecting you to your dashboard!',
           })
        
           router.push(callbackUrl ? callbackUrl : '/')
          } catch (error) {
           console.log({ error })
           toast({
            title: 'Something went wrong!',
            description: "We couldn't create your account. Please try again later!",
            variant: 'destructive',
           })
          } finally {
           setIsLoading(false)
          }
         }
        
        ...
        

        Email verification

        Вот и все! 🎉

        Пользователь сможет подтвердить свой адрес электронной почты и завершить регистрацию в нашем приложении.

        🧑‍💻 Репозиторий здесь

        Заключение

        Мы уже знаем, как создавать и отправлять электронные письма с помощью React Email и Resend. Этот процесс позволит вам использовать свои знания React для эффективного создания электронных писем, сохраняя при этом привычный и продуктивный рабочий процесс.

        Вы можете экспериментировать с различными компонентами и свойствами, чтобы создавать электронные письма, идеально соответствующие потребностям ваших проектов.


        Хотите связаться с автором?

        Люблю общаться с друзьями со всего мира на 𝕏.


        Также опубликовано здесь


        Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE