Postgres + Auth.js v5: подключаем БД и magic-link
Тема 4/5: День 4. Доходный ИИ-сервис — собираем с нуля → Урок 4/7
Без авторизации сервис не работает — некому списывать лимиты и нечего продавать. На этом уроке подключаем Postgres (для теста — Neon, бесплатно), описываем схему через Prisma и добавляем Auth.js v5 с magic-link на email. Без паролей и без OAuth — это самый простой и безопасный путь.
Из чего состоит авторизация
Авторизация складывается из двух связанных частей: базы данных, где хранятся пользователи, сессии и токены, и библиотеки, которая с этой базой работает — шлёт письма и проверяет сессии. В нашем стеке роли распределены так. Базой служит Postgres: на стадии теста мы берём Neon — бесплатный облачный Postgres с регистрацией через GitHub, а для продакшена в РФ позже переедем на Timeweb Managed Postgres. Роль ORM выполняет Prisma: схему базы мы описываем в одном файле, а Prisma генерирует типизированный клиент, так что в коде получается prisma.user.findMany() с автодополнением. За саму авторизацию отвечает Auth.js v5 — npm-пакет, который раньше назывался NextAuth; все данные остаются в нашей базе, без внешних сервисов.
Важно про 152-ФЗ. Neon — это западный сервис с серверами в США/EU. По 152-ФЗ первичная обработка персональных данных граждан РФ должна быть на серверах в России. На этапе тестового запуска Neon допустим (быстро, бесплатно, удобно). Для боевого сервиса с реальными пользователями — переезжаем на российский managed Postgres (Timeweb, VK Cloud, Selectel). Об этом — в платном курсе.
Создание Postgres-проекта на Neon
На время теста заводим базу на Neon — это несколько шагов:
- Заходите на neon.tech → Sign up через GitHub
- Жмёте «New Project»
- Указываете имя (например, «ai-posts»), регион (Frankfurt — ближе к РФ)
- Ждёте ~10 секунд пока проект создаётся
- На главной странице проекта копируете Connection string вида
postgresql://user:pass@host/db?sslmode=require
Эта строка пойдёт в .env.local как DATABASE_URL.
Промпт для подключения Auth.js v5 + Prisma
Подключи Postgres через Prisma и Auth.js v5 к нашему Next.js проекту.
DATABASE_URL: postgresql://... (вставь из Neon)
Сделай:
1. Установи пакеты: prisma, @prisma/client, @auth/core, @auth/prisma-adapter
2. Инициализируй Prisma: npx prisma init
3. В prisma/schema.prisma опиши модели User, Account, Session, VerificationToken по стандарту Auth.js + кастомные поля:
User: id, email, emailVerified, name, image, plan ("free" default), creditsUsed (int default 0), creditsLimit (int default 5), createdAt
4. Запусти миграцию: npx prisma migrate dev --name init
5. Создай src/lib/db.ts с экспортом Prisma client (singleton)
6. Создай src/auth.ts с Auth.js v5 конфигом: PrismaAdapter, session strategy "database", email-провайдер (magic-link)
7. Создай src/app/api/auth/[...nextauth]/route.ts — handlers для GET и POST
Дальше создай страницы:
- /signup — форма с email и кнопкой «Получить magic-link»
- /login — то же самое
- /dashboard — пока пустая, доступна только авторизованным
Env-переменные: DATABASE_URL, AUTH_SECRET (сгенерируй через openssl rand -hex 32), AUTH_URL (http://localhost:3000), EMAIL_FROM, EMAIL_SERVER (SMTP-строка от Notisend или Resend).
Стиль — как на остальных страницах (тёмная тема, бирюзовый акцент). Тексты на русском.
Как работает magic-link
- Пользователь на /signup вводит email
- Интерфейс шлёт запрос в Auth.js: «отправь magic-link на этот email»
- Auth.js генерирует одноразовый токен, сохраняет в таблицу VerificationToken, отправляет email со ссылкой
https://yoursite.com/api/auth/callback/email?token=... - Пользователь кликает ссылку в почте → попадает на callback
- Auth.js проверяет токен, создаёт User (если новый) и Session, ставит httpOnly cookie
- Пользователь авторизован — сессия живёт 30 дней по умолчанию
У такого подхода есть понятные преимущества перед паролями. Пользователю не нужно ничего запоминать — он всё равно вводит только email. Перебором паролей такую авторизацию не взломать, потому что паролей просто нет. Исчезает и целый сценарий «забыл пароль» с восстановлением. А регистрация и вход при этом превращаются в одну и ту же форму.
Защищённые роуты
Страница /dashboard должна быть доступна только авторизованным. Добавим проверку:
В Next.js 16 защити /dashboard через proxy (в Next.js 16 middleware переименован в proxy).
Создай src/proxy.ts: используй функцию auth() из Auth.js v5. Если сессии нет — редирект на /login?from=<исходный URL>.
Защищай: /dashboard, /api/generate (которая будет позже), /api/account.
Не защищай: /, /pricing, /signup, /login, /api/auth/*.
Модели в БД
Auth.js v5 + PrismaAdapter работает с четырьмя стандартными моделями:
- User — пользователь (id, email, name, image, emailVerified). Создаётся при первой авторизации. К нему мы добавили бизнес-поля: plan, creditsUsed, creditsLimit.
- Account — связь User с провайдером авторизации. Один User может иметь несколько Account (например, magic-link + Google в будущем).
- Session — активная сессия (sessionToken, userId, expires). По дефолту живёт 30 дней.
- VerificationToken — одноразовые токены для magic-link. Auth.js сам создаёт и удаляет.
На уроке 24 мы будем списывать creditsUsed при каждой генерации, а при оплате — обновлять plan.
Безопасность
Главное правило: никогда не коммитьте .env.local в git. Там лежат DATABASE_URL и AUTH_SECRET. Если случайно закоммитили — сразу пересоздайте: Neon → Settings → Reset password, новый AUTH_SECRET через openssl rand -hex 32.
Каждый запрос к БД фильтруйте по userId. В Auth.js v5 сессия даёт session.user.id — используйте его в каждом where. Если запрос к чужим данным не фильтруется по userId — это дыра: любой авторизованный пользователь сможет прочитать чужие записи.
Если что-то не работает
При первом подключении авторизации чаще всего всплывают четыре типовые проблемы:
- Magic-link не приходит — проверьте папку «Спам». Если шлёте через SMTP-провайдера (Notisend, Resend) — убедитесь что домен отправителя верифицирован (SPF/DKIM-записи в DNS). Без верификации письма падают в спам или вообще не доставляются.
- «PrismaClientInitializationError» — проверьте DATABASE_URL в .env.local. Часто забывают
?sslmode=requireв конце для Neon. - «User not authenticated» — куки не передаются. Проверьте что AUTH_URL совпадает с реальным URL приложения (http://localhost:3000 для dev, продакшен-домен для боевого).
- «No matching VerificationToken» — токен уже использован или истёк. Токены живут 24 часа, потом нужно запрашивать новый magic-link.
Что в следующем уроке
Авторизация готова: пользователь регистрируется и попадает в /dashboard, но там пока пусто. На уроке 24 добавляем главную функцию сервиса — ИИ-генерацию через OpenRouter.