Аутентификация: пароли, сессии, JWT
Аутентификация — «откуда сайт знает что это ты». Тема большая, но базовые принципы покрывают 95% случаев. Главное не катастрофически налажать — потерянный пароль клиента это очень плохо.
Хешируй пароли — НИКОГДА не храни как есть
Самое важное правило: пароль пользователя НИКОГДА не должен попасть в БД как есть. Если у тебя там password: "qwerty" — при утечке БД злоумышленник получает пароли всех. Решение: bcrypt (или argon2). Когда регистрация: bcrypt.hash(password, 10) → в БД $2b$10$..... При логине:
→ true/false. Хеш необратим.
Сессии vs JWT — две модели
Сессия: сервер хранит «активные пользователи» в БД/Redis, клиенту даёт случайный sessionId в cookie. Каждый запрос — сервер ищет сессию по id. Просто отзывать (удалил из БД — выход). Минус: нужен storage. JWT (JSON Web Token): подписанный токен с данными внутри (id, email). Серверу не нужен storage — он проверяет подпись. Минус: невозможно отозвать до истечения. На практике для веба — сессии лучше; для микросервисов — JWT.
NextAuth (Auth.js) — стандарт для Next.js
Не пиши auth с нуля — используй Auth.js (раньше NextAuth). Готовые провайдеры: Google, GitHub, Email-magic-link, credentials. Обработка сессий, cookies, CSRF — всё под капотом. Конфиг в
На клиенте: useSession(). На сервере: getServerSession(). Хочешь свой логин-пароль? — провайдер Credentials плюс твоя проверка через bcrypt.
Cookie с правильными флагами
Cookie с сессией ОБЯЗАНА быть: httpOnly (JS не читает — защита от XSS). secure (только по HTTPS). sameSite: lax или strict (защита от CSRF). Без этих флагов — твоя сессия украдётся первой попавшейся уязвимостью. Auth.js ставит правильно по умолчанию, но если пишешь сам — не забудь.
OAuth — войти через Google/GitHub
Пользователь не хочет регистрироваться. Решение: «Войти через Google». Это OAuth 2.0. Регистрируешь приложение в Google Cloud Console → получаешь client_id и client_secret → кладёшь в .env. NextAuth уже умеет — добавляешь GoogleProvider в конфиг и работает. Главный плюс: никаких паролей не хранишь, ответственность на Google. 90% продуктов используют только OAuth, без своих паролей.
// === Установка ===
// npm i next-auth bcryptjs
// npm i -D @types/bcryptjs
// === schema.prisma ===
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
name String?
createdAt DateTime @default(now())
}
// === app/api/auth/[...nextauth]/route.ts ===
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import GoogleProvider from 'next-auth/providers/google';
import bcrypt from 'bcryptjs';
import { prisma } from '@/lib/prisma';
const handler = NextAuth({
providers: [
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Пароль', type: 'password' },
},
async authorize(creds) {
if (!creds?.email || !creds?.password) return null;
const user = await prisma.user.findUnique({
where: { email: creds.email },
});
if (!user) return null;
const ok = await bcrypt.compare(creds.password, user.passwordHash);
if (!ok) return null;
return { id: user.id, email: user.email, name: user.name };
},
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
session: { strategy: 'jwt' }, // или 'database' для DB-сессий
pages: {
signIn: '/login',
},
});
export { handler as GET, handler as POST };
// === Регистрация ===
// app/api/register/route.ts
import bcrypt from 'bcryptjs';
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
export async function POST(req: Request) {
const { email, password, name } = await req.json();
if (!email || !password || password.length < 8) {
return NextResponse.json({ error: 'Неверные данные' }, { status: 400 });
}
const exists = await prisma.user.findUnique({ where: { email } });
if (exists) {
return NextResponse.json({ error: 'Email уже занят' }, { status: 409 });
}
const passwordHash = await bcrypt.hash(password, 10);
const user = await prisma.user.create({
data: { email, passwordHash, name },
});
return NextResponse.json({ id: user.id });
}
// === КЛИЕНТСКИЙ ХУК ===
'use client';
import { useSession, signIn, signOut } from 'next-auth/react';
export function UserBadge() {
const { data: session, status } = useSession();
if (status === 'loading') return <p>...</p>;
if (!session) {
return <button onClick={() => signIn()}>Войти</button>;
}
return (
<>
<span>{session.user?.email}</span>
<button onClick={() => signOut()}>Выйти</button>
</>
);
}
// === ЗАЩИТА API ===
// app/api/me/route.ts
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
export async function GET() {
const session = await getServerSession(authOptions);
if (!session) {
return new Response('Unauthorized', { status: 401 });
}
return Response.json({ email: session.user?.email });
}Auth.js + Credentials с bcrypt — рабочая схема