Багато розробників на початку кар'єри, коли чують слово "автентифікація", відчувають холодок по спині, адже все, що стосується безпеки, здається складним і важким у реалізації, але це не зовсім так.
Тому я вирішив створити дуже простий туторіал, щоб показати, що автентифікація — це не та страшна сила, якою ви її вважаєте.
Я використовую Next.js, але логіка схожа для інших фреймворків, оскільки я не буду використовувати жодних бібліотек — створимо все з нуля.
Чому використовувати автентифікацію
Автентифікація гарантує, що лише авторизовані користувачі матимуть доступ до певних частин вашого додатку. Хороший приклад — сайти електронної комерції, де можна переглядати продукти, але інформацію про замовлення можна побачити лише після того, як ви увійдете в систему.
Діаграма нижче є частиною офіційної документації Next і наочно показує, як працює процес автентифікації:
Автентифікація зазвичай починається з процесу реєстрації користувача для подальшої автентифікації. Отже, давайте створимо дуже просту форму для збору імені користувача, електронної пошти та пароля.
Створення форми в Next
Перш за все, створимо форму для отримання деталей користувача. Коли форма буде надіслана, вона створить дію на сервері.
import { useServer } from 'next/server';
import { useActionState } from 'next/action';
function SignUpForm() {
const action = useServer('signupAction');
const { pending, error } = useActionState(action);
return (
{pending ? 'Submitting...' : 'Sign Up'}
{error &&
{error.message}
}
); }
Налаштування сервера для отримання форми
Тепер створимо новий файл, де напишемо функцію сервера, яка оброблятиме надсилання форми.
По-перше, перевіримо поля вводу перед тим, як виконувати будь-яку логіку автентифікації.
import { z } from 'zod';
import { hash } from 'bcryptjs';
import { prisma } from '../lib/prisma';
import { createSession } from '../lib/session';
export const signupAction = async (formData) => {
const schema = z.object({
name: z.string().min(1),
email: z.string().email(),
password: z.string().min(6),
});
const { success, error } = schema.safeParse(formData);
if (!success) {
return { error: 'Invalid input' };
}
const { name, email, password } = formData;
const hashedPassword = await hash(password, 10);
const user = await prisma.user.create({
data: { name, email, password: hashedPassword },
});
const session = await createSession(user.id);
return { session };
};
Керування сесіями
Для збереження сесій користувачів на всіх запитах створимо файл, який міститиме всю логіку керування сесією, включаючи функції для створення, перевірки, оновлення та видалення сесій.
import { sign, verify } from 'jsonwebtoken';
import { serialize, parse } from 'cookie';
const secretKey = process.env.JWT_SECRET;
export const createSession = (userId) => {
const token = sign({ userId }, secretKey, { expiresIn: '1h' });
const cookie = serialize('session', token, { httpOnly: true, maxAge: 3600 });
return { cookie, userId };
};
export const verifySession = (req) => {
const { session } = parse(req.headers.cookie || '');
if (!session) return null;
try {
const payload = verify(session, secretKey);
return payload.userId;
} catch {
return null;
}
};
export const deleteSession = () => {
const cookie = serialize('session', '', { httpOnly: true, maxAge: -1 });
return { cookie };
};
Інтеграція створення сесії
Коли користувач зареєструється через нашу форму, ми викликаємо функцію createSession, щоб створити сесію для користувача після успішного створення акаунта.
const { name, email, password } = formData;
const hashedPassword = await hash(password, 10);
const user = await prisma.user.create({
data: { name, email, password: hashedPassword },
});
const { cookie, userId } = await createSession(user.id);
return {
headers: { 'Set-Cookie': cookie },
userId,
};
Далі нам потрібно вирішити, до яких маршрутів і даних користувач може отримати доступ залежно від його ролей чи дозволів.
Це відомо як авторизація.
Middleware для перевірки авторизації
Ми можемо обробляти логіку авторизації в middleware, і таким чином перевіряти, чи поточний маршрут захищений або доступний для доступу. Якщо маршрут захищений, користувач буде перенаправлений на екран входу.
import { NextResponse } from 'next/server';
import { verifySession } from '../lib/session';
export function middleware(req) {
const userId = verifySession(req);
if (!userId && req.url.pathname.startsWith('/dashboard')) {
return NextResponse.redirect('/login');
}
return NextResponse.next();
}
Захист даних за допомогою шару доступу до даних
Для забезпечення консистентності коду корисно зберігати логіку авторизації поруч із місцем, де дані запитуються, використовуючи шар доступу до даних.
import { prisma } from '../lib/prisma';
import { verifySession } from '../lib/session';
export const getUser = async (req) => {
const userId = verifySession(req);
if (!userId) throw new Error('Unauthorized');
const user = await prisma.user.findUnique({ where: { id: userId } });
return user;
};
Порада з безпеки: щоб зменшити ризик витоку даних, варто отримувати лише ті дані, які є строго необхідними від API.
export const getUser = async (req) => {
const userId = verifySession(req);
if (!userId) throw new Error('Unauthorized');
const user = await prisma.user.findUnique({
where: { id: userId },
select: { name: true, email: true },
});
return user;
};
Якщо хочете дізнатися більше про автентифікацію з Next, обов'язково ознайомтеся з їхньою документацією, вона дуже детальна та повна. Сподіваюся, цей посібник допоможе вам зрозуміти принципи автентифікації в додатках Next.js.
Перекладено з: Domine a autenticação no Next.js: Guia descomplicado 🛡️✨