Внимание:Статья была обновлена более года назад - 20 июля 2024. Информация может быть не актуальной
Аутентификация в Next.js может быть достаточно простой и удобной благодаря использованию серверных компонентов, Actions и Middleware. Давайте рассмотрим, как можно реализовать аутентификацию с помощью JWT токенов и сессий, используя Prisma для работы с базой данных.
Вы можете выбрать любую базу данных, поддерживаемую для вашего проекта: , , и т. д. В этом примере мы используем , но вы можете заменить URL подключения на параметры вашей базы данных.
В корневой папке проекта создайте файл и добавьте строку с URL подключения к базе данных:
Замените , , , и на соответствующие значения вашей базы данных.
Создаём и обрабатываем JWT токены и сессии в файлах и
В реализованы функции для работы с сессией:
Создание сессии: Функция генерирует JWT с данными пользователя и устанавливает его в куки с параметрами безопасности (например, , , ).
Проверка сессии: Функция извлекает JWT из куки и проверяет его действительность. Если сессия действительна, возвращает информацию о пользователе; иначе перенаправляет пользователя на страницу логина.
Удаление сессии: Функция удаляет куки сессии, тем самым завершив сеанс пользователя и перенаправив его на страницу логина.
В файле определены схемы валидации , которые гарантируют, что данные пользователя соответствуют ожиданиям: обязательные поля, минимальная длина и соответствие регулярным выражениям для пароля.
1import{ z }from'zod';23exportconst SignupFormSchema = z.object({4 name: z
5.string()6.min(2,{ message:'Name must be at least 2 characters long.'})7.trim(),8 username: z
9.string()10.min(4,{ message:'Be at least 4 characters long'})11.trim(),12 password: z
13.string()14.min(8,{ message:'Be at least 8 characters long'})15.regex(/[a-zA-Z]/,{ message:'Contain at least one letter.'})16.regex(/[0-9]/,{ message:'Contain at least one number.'})17.regex(/[^a-zA-Z0-9]/,{18 message:'Contain at least one special character.',19})20.trim(),21});2223exportconst LoginFormSchema = z.object({24 username: z.string().min(1,{ message:'Username field must not be empty.'}),25 password: z.string().min(1,{ message:'Password field must not be empty.'}),26});2728exporttypeFormState=29|{30 errors?:{31 username?:string[];32 password?:string[];33 name?:string[];34};35 message?:string;36}37|undefined;3839exporttypeSessionPayload={40 userId:string|number;41 expiresAt: Date;42};
features/auth/services/login.ts
LoginFormSchema
login.ts
1'use server';23import{ PrismaClient }from'@prisma/client';4import bcrypt from'bcryptjs';5import{ createSession }from'../api/session';6import{ FormState, LoginFormSchema }from'../types/definitions';78const prisma =newPrismaClient();910exportasyncfunctionlogin(11 state: FormState,12 formData: FormData,13):Promise<FormState>{14// 1. Validate form fields15const validatedFields = LoginFormSchema.safeParse({16 username: formData.get('username'),17 password: formData.get('password'),18});19const errorMessage ={ message:'Invalid login credentials.'};2021// If any form fields are invalid, return early22if(!validatedFields.success){23return{24 errors: validatedFields.error.flatten().fieldErrors,25};26}2728// 2. Query the database for the user with the given email29const{ username, password }= validatedFields.data;3031const user =await prisma.user.findFirst({32 where:{ username },33});3435if(!user){36return errorMessage;37}3839// 3. Compare the user's password with the hashed password in the database40const passwordMatch =await bcrypt.compare(password, user.password);4142// If the password does not match, return early43if(!passwordMatch){44return errorMessage;45}4647// 4. If login successful, create a session for the user and redirect48const userId = user.id.toString();49awaitcreateSession(userId);50}
1'use server';23import{ PrismaClient }from'@prisma/client';4import bcrypt from'bcryptjs';5import{ createSession }from'../api/session';6import{ FormState, SignupFormSchema }from'../types/definitions';78const prisma =newPrismaClient();910exportasyncfunctionsignup(11 state: FormState,12 formData: FormData,13):Promise<FormState>{14// 1. Validate form fields15const validatedFields = SignupFormSchema.safeParse({16 name: formData.get('name'),17 username: formData.get('username'),18 password: formData.get('password'),19});2021// If any form fields are invalid, return early22if(!validatedFields.success){23return{24 errors: validatedFields.error.flatten().fieldErrors,25};26}2728// 2. Prepare data for insertion into database29const{ name, username, password }= validatedFields.data;3031// 3. Check if the user's username already exists32const existingUser =await prisma.user.findFirst({ where:{ username }});3334if(existingUser){35return{36 message:'Username already exists, please use a different username.',37};38}3940// Hash the user's password41const hashedPassword =await bcrypt.hash(password,10);4243// 3. Insert the user into the database or call an Auth Provider's API44const user =await prisma.user.create({45 data:{46 name,47 username,48 password: hashedPassword,49},50 select:{ id:true},51});5253if(!user){54return{55 message:'An error occurred while creating your account.',56};57}5859// 4. Create a session for the user60const userId = user.id.toString();61awaitcreateSession(userId);62}