EvolCode
треки/Сервер и продакшн·04 / 08
8 мин чтения

Аутентификация: пароли, сессии, JWT

Аутентификация — «откуда сайт знает что это ты». Тема большая, но базовые принципы покрывают 95% случаев. Главное не катастрофически налажать — потерянный пароль клиента это очень плохо.

Хешируй пароли — НИКОГДА не храни как есть

Самое важное правило: пароль пользователя НИКОГДА не должен попасть в БД как есть. Если у тебя там password: "qwerty" — при утечке БД злоумышленник получает пароли всех. Решение: bcrypt (или argon2). Когда регистрация: bcrypt.hash(password, 10) → в БД $2b$10$..... При логине:

$ bcrypt.compare(input, savedHash)

→ 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 — всё под капотом. Конфиг в

$ app/api/auth/[...nextauth]/route.ts

На клиенте: 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, без своих паролей.

примерtypescript
// === Установка ===
// 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 — рабочая схема

главное
  • 01Пароли ВСЕГДА хешируй через bcrypt (или argon2) — никогда plain text
  • 02Сессии: серверный storage + cookie. JWT: подписанный токен без storage
  • 03NextAuth/Auth.js — стандарт для Next.js, не пиши auth вручную
  • 04Cookie должен быть httpOnly + secure + sameSite — иначе уязвимости
  • 05OAuth (Google, GitHub) — убирает headache своих паролей, 90% случаев хватает
  • 06Регистрация: проверь длину пароля, уникальность email, bcrypt.hash перед сохранением
  • 07Защита API роута: getServerSession в начале, без сессии → 401
проверь себя
01В БД нашли поле password со значениями "qwerty", "12345". Что это значит?
02В чём принципиальная разница сессии и JWT?
03Cookie с сессией без флага httpOnly. В чём риск?
следующий урок: Деплой на VPS: SSH, nginx, systemd