Как реализовать аутентификацию в Next.js
Понимание аутентификации критически важно для защиты данных вашего приложения. На этой странице объясняется, какие функции React и Next.js использовать для реализации аутентификации.
Перед началом работы полезно разбить процесс на три концепции:
- Аутентификация: Проверяет, является ли пользователь тем, за кого себя выдает. Требует от пользователя подтверждения личности с помощью чего-то, что у него есть, например, имени пользователя и пароля.
- Управление сеансом: Отслеживает состояние аутентификации пользователя между запросами.
- Авторизация: Определяет, к каким маршрутам и данным пользователь может получить доступ.
На этой диаграмме показан процесс аутентификации с использованием функций React и Next.js:

Примеры на этой странице демонстрируют базовую аутентификацию по имени пользователя и паролю в учебных целях. Хотя вы можете реализовать собственное решение для аутентификации, для повышения безопасности и упрощения мы рекомендуем использовать библиотеку аутентификации. Они предлагают встроенные решения для аутентификации, управления сеансами и авторизации, а также дополнительные функции, такие как социальные входы, многофакторная аутентификация и управление доступом на основе ролей. Список можно найти в разделе Библиотеки аутентификации.
Аутентификация
Вот шаги для реализации формы регистрации и/или входа:
- Пользователь отправляет свои учетные данные через форму.
- Форма отправляет запрос, который обрабатывается API-маршрутом.
- После успешной проверки процесс завершается, указывая на успешную аутентификацию пользователя.
- Если проверка не удалась, отображается сообщение об ошибке.
Рассмотрим форму входа, где пользователи могут ввести свои учетные данные:
import { FormEvent } from 'react'
import { useRouter } from 'next/router'
export default function LoginPage() {
const router = useRouter()
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const email = formData.get('email')
const password = formData.get('password')
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
if (response.ok) {
router.push('/profile')
} else {
// Обработка ошибок
}
}
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" placeholder="Email" required />
<input type="password" name="password" placeholder="Пароль" required />
<button type="submit">Войти</button>
</form>
)
}
import { FormEvent } from 'react'
import { useRouter } from 'next/router'
export default function LoginPage() {
const router = useRouter()
async function handleSubmit(event) {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const email = formData.get('email')
const password = formData.get('password')
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
if (response.ok) {
router.push('/profile')
} else {
// Обработка ошибок
}
}
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" placeholder="Email" required />
<input type="password" name="password" placeholder="Пароль" required />
<button type="submit">Войти</button>
</form>
)
}
Форма выше имеет два поля ввода для email и пароля пользователя. При отправке она вызывает функцию, которая отправляет POST-запрос к API-маршруту (/api/auth/login
).
Затем вы можете вызвать API вашего провайдера аутентификации в API-маршруте для обработки аутентификации:
import type { NextApiRequest, NextApiResponse } from 'next'
import { signIn } from '@/auth'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const { email, password } = req.body
await signIn('credentials', { email, password })
res.status(200).json({ success: true })
} catch (error) {
if (error.type === 'CredentialsSignin') {
res.status(401).json({ error: 'Неверные учетные данные.' })
} else {
res.status(500).json({ error: 'Что-то пошло не так.' })
}
}
}
import { signIn } from '@/auth'
export default async function handler(req, res) {
try {
const { email, password } = req.body
await signIn('credentials', { email, password })
res.status(200).json({ success: true })
} catch (error) {
if (error.type === 'CredentialsSignin') {
res.status(401).json({ error: 'Неверные учетные данные.' })
} else {
res.status(500).json({ error: 'Что-то пошло не так.' })
}
}
}
Управление сессиями
Управление сессиями гарантирует сохранение аутентифицированного состояния пользователя между запросами. Оно включает создание, хранение, обновление и удаление сессий или токенов.
Существует два типа сессий:
- Stateless (без состояния): Данные сессии (или токен) хранятся в cookies браузера. Cookie отправляется с каждым запросом, позволяя проверять сессию на сервере. Этот метод проще, но может быть менее безопасным, если реализован неправильно.
- Database (база данных): Данные сессии хранятся в базе данных, а браузер пользователя получает только зашифрованный ID сессии. Этот метод безопаснее, но сложнее и требует больше ресурсов сервера.
Полезно знать: Хотя вы можете использовать любой метод или оба, мы рекомендуем использовать библиотеки управления сессиями, такие как iron-session или Jose.
Stateless сессии
Установка и удаление cookies
Вы можете использовать API Routes для установки сессии в виде cookie на сервере:
import { serialize } from 'cookie'
import type { NextApiRequest, NextApiResponse } from 'next'
import { encrypt } from '@/app/lib/session'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const sessionData = req.body
const encryptedSessionData = encrypt(sessionData)
const cookie = serialize('session', encryptedSessionData, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // Одна неделя
path: '/',
})
res.setHeader('Set-Cookie', cookie)
res.status(200).json({ message: 'Successfully set cookie!' })
}
import { serialize } from 'cookie'
import { encrypt } from '@/app/lib/session'
export default function handler(req, res) {
const sessionData = req.body
const encryptedSessionData = encrypt(sessionData)
const cookie = serialize('session', encryptedSessionData, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // Одна неделя
path: '/',
})
res.setHeader('Set-Cookie', cookie)
res.status(200).json({ message: 'Successfully set cookie!' })
}
Сессии в базе данных
Чтобы создать и управлять сессиями в базе данных, вам нужно выполнить следующие шаги:
- Создать таблицу в вашей базе данных для хранения данных сессии (или проверить, поддерживает ли это ваша библиотека аутентификации).
- Реализовать функциональность для добавления, обновления и удаления сессий.
- Зашифровать идентификатор сессии перед сохранением в браузере пользователя и обеспечить синхронизацию между базой данных и cookie (это опционально, но рекомендуется для оптимистичных проверок аутентификации в Middleware).
Создание сессии на сервере:
import db from '../../lib/db'
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const user = req.body
const sessionId = generateSessionId()
await db.insertSession({
sessionId,
userId: user.id,
createdAt: new Date(),
})
res.status(200).json({ sessionId })
} catch (error) {
res.status(500).json({ error: 'Internal Server Error' })
}
}
import db from '../../lib/db'
export default async function handler(req, res) {
try {
const user = req.body
const sessionId = generateSessionId()
await db.insertSession({
sessionId,
userId: user.id,
createdAt: new Date(),
})
res.status(200).json({ sessionId })
} catch (error) {
res.status(500).json({ error: 'Internal Server Error' })
}
}
Авторизация
После аутентификации пользователя и создания сессии вы можете реализовать авторизацию для контроля доступа пользователей к функциям вашего приложения.
Существует два основных типа проверок авторизации:
- Оптимистичные: Проверяют, авторизован ли пользователь для доступа к маршруту или выполнения действия, используя данные сессии, хранящиеся в cookie. Эти проверки полезны для быстрых операций, таких как отображение/скрытие элементов интерфейса или перенаправление пользователей на основе разрешений или ролей.
- Безопасные: Проверяют, авторизован ли пользователь для доступа к маршруту или выполнения действия, используя данные сессии, хранящиеся в базе данных. Эти проверки более безопасны и используются для операций, требующих доступа к конфиденциальным данным или действиям.
Для обоих случаев мы рекомендуем:
- Создать Слой доступа к данным (DAL) для централизации логики авторизации
- Использовать Объекты передачи данных (DTO) для возврата только необходимых данных
- Опционально использовать Middleware для выполнения оптимистичных проверок.
Оптимистичные проверки с Middleware (Опционально)
В некоторых случаях вы можете использовать Middleware и перенаправлять пользователей на основе разрешений:
- Для выполнения оптимистичных проверок. Поскольку Middleware выполняется для каждого маршрута, это хороший способ централизовать логику перенаправления и предварительно фильтровать неавторизованных пользователей.
- Для защиты статических маршрутов, которые используют общие данные между пользователями (например, контент за платным доступом).
Однако, поскольку Middleware выполняется для каждого маршрута, включая предварительно загруженные маршруты, важно читать сессию только из cookie (оптимистичные проверки) и избегать проверок в базе данных, чтобы предотвратить проблемы с производительностью.
Например:
import { NextRequest, NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'
// 1. Указать защищенные и публичные маршруты
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
export default async function middleware(req: NextRequest) {
// 2. Проверить, является ли текущий маршрут защищенным или публичным
const path = req.nextUrl.pathname
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)
// 3. Расшифровать сессию из cookie
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
// 4. Перенаправить на /login, если пользователь не аутентифицирован
if (isProtectedRoute && !session?.userId) {
return NextResponse.redirect(new URL('/login', req.nextUrl))
}
// 5. Перенаправить на /dashboard, если пользователь аутентифицирован
if (
isPublicRoute &&
session?.userId &&
!req.nextUrl.pathname.startsWith('/dashboard')
) {
return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
}
return NextResponse.next()
}
// Маршруты, на которых Middleware не должен выполняться
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
import { NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'
// 1. Указать защищенные и публичные маршруты
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
export default async function middleware(req) {
// 2. Проверить, является ли текущий маршрут защищенным или публичным
const path = req.nextUrl.pathname
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)
// 3. Расшифровать сессию из cookie
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
// 5. Перенаправить на /login, если пользователь не аутентифицирован
if (isProtectedRoute && !session?.userId) {
return NextResponse.redirect(new URL('/login', req.nextUrl))
}
// 6. Перенаправить на /dashboard, если пользователь аутентифицирован
if (
isPublicRoute &&
session?.userId &&
!req.nextUrl.pathname.startsWith('/dashboard')
) {
return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
}
return NextResponse.next()
}
// Маршруты, на которых Middleware не должен выполняться
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
Хотя Middleware может быть полезен для начальных проверок, он не должен быть единственной линией защиты ваших данных. Большинство проверок безопасности должно выполняться как можно ближе к источнику данных, см. Слой доступа к данным (DAL) для получения дополнительной информации.
Советы:
- В Middleware вы также можете читать cookie с помощью
req.cookies.get('session').value
.- Middleware использует Edge Runtime, проверьте, совместимы ли ваша библиотека аутентификации и библиотека управления сессиями.
- Вы можете использовать свойство
matcher
в Middleware, чтобы указать, на каких маршрутах должен выполняться Middleware. Однако для аутентификации рекомендуется выполнять Middleware на всех маршрутах.
Создание уровня доступа к данным (DAL)
Защита API-маршрутов
API-маршруты в Next.js необходимы для обработки серверной логики и управления данными. Важно защитить эти маршруты, чтобы гарантировать, что только авторизованные пользователи имеют доступ к определенным функциям. Обычно это включает проверку статуса аутентификации пользователя и его прав на основе ролей.
Вот пример защиты API-маршрута:
import { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await getSession(req)
// Проверяем, авторизован ли пользователь
if (!session) {
res.status(401).json({
error: 'Пользователь не авторизован',
})
return
}
// Проверяем, есть ли у пользователя роль 'admin'
if (session.user.role !== 'admin') {
res.status(401).json({
error: 'Несанкционированный доступ: пользователь не имеет прав администратора.',
})
return
}
// Продолжаем выполнение маршрута для авторизованных пользователей
// ... реализация API-маршрута
}
export default async function handler(req, res) {
const session = await getSession(req)
// Проверяем, авторизован ли пользователь
if (!session) {
res.status(401).json({
error: 'Пользователь не авторизован',
})
return
}
// Проверяем, есть ли у пользователя роль 'admin'
if (session.user.role !== 'admin') {
res.status(401).json({
error: 'Несанкционированный доступ: пользователь не имеет прав администратора.',
})
return
}
// Продолжаем выполнение маршрута для авторизованных пользователей
// ... реализация API-маршрута
}
Этот пример демонстрирует API-маршрут с двухуровневой проверкой безопасности для аутентификации и авторизации. Сначала проверяется активная сессия, затем подтверждается, что вошедший пользователь является 'администратором'. Такой подход обеспечивает безопасный доступ, ограниченный для авторизованных пользователей, поддерживая надежную защиту при обработке запросов.
Ресурсы
Теперь, когда вы узнали об аутентификации в Next.js, вот совместимые библиотеки и ресурсы, которые помогут реализовать безопасную аутентификацию и управление сессиями:
Библиотеки аутентификации
Библиотеки управления сессиями
Дополнительные материалы
Чтобы продолжить изучение аутентификации и безопасности, ознакомьтесь со следующими ресурсами: