Аутентификация с токенами в Next.js: реализация на JWT
TL;DR
Коротко: в Next.js можно реализовать токенную аутентификацию с помощью JWT и библиотеки jose, сохраняя токен в cookie. В статье показан шаг‑за‑шаг процесс: создание формы входа, API‑эндпоинта с подписью JWT, средство проверки токена, middleware для защиты маршрутов и клиентский хук для состояния аутентификации. Внизу — чеклисты, сценарии тестирования и рекомендации по безопасности.
Важно: представленный код — демонстрационный. Для продакшна используйте надёжное хранилище пользователей, защищённые cookie и механизмы обновления токенов.

Token authentication — популярная стратегия защиты веб‑ и мобильных приложений от неавторизованного доступа. В Next.js можно использовать готовые решения вроде NextAuth, но при необходимости получить полный контроль, вы можете реализовать собственную систему с JSON Web Tokens (JWT).
Ниже приведён подробный туториал для Next.js 13 (директория app). Все шаги — от инициализации проекта до защиты маршрутов через middleware — с примерами кода.
Основные понятия в одну фразу
- JWT: компактный токен для передачи утверждений о пользователе, подписывается секретом.
- HttpOnly cookie: cookie, недоступные JavaScript, снижают риск XSS.
- Middleware: серверный промежуточный код, который проверяет запросы перед рендерингом.
Альтернативные подходы (коротко)
- NextAuth: готовое решение с OAuth, база провайдеров и сессиями.
- Сеансовая аутентификация (server sessions): с хранением сессий на сервере или в Redis.
- OAuth2 / OpenID Connect: для федеративного входа через внешнего провайдера.
Требования и зависимости
Установите Next.js и необходимые пакеты:
npx create-next-app@latest next-auth-jwt --experimental-appЗатем в проекте установите:
npm install jose universal-cookie- jose — утилиты для создания и проверки JWT.
- universal-cookie — удобная работа с cookie на клиенте и сервере.
1. Интерфейс формы входа
Создайте src/app/login/page.js и добавьте компонент страницы с формой входа. Компонент должен быть клиентским (use client) — так Next.js отделяет код, выполняющийся в браузере.
"use client";
import { useRouter } from "next/navigation";
export default function LoginPage() {
const router = useRouter();
const handleSubmit = async (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const username = formData.get("username");
const password = formData.get("password");
const res = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({ username, password }),
headers: { "content-type": "application/json" },
});
const { success } = await res.json();
if (success) {
router.push("/protected");
router.refresh();
} else {
alert("Login failed");
}
};
return (
);
}Примечание: use client указывает Next.js, что компонент рендерится на клиенте. handleSubmit выполняется в браузере.
2. API‑эндпоинт входа и генерация JWT
Создайте src/app/api/login/route.js. В примере ниже используются мок‑данные: username “admin” и password “admin”. В реальном проекте сравнивайте с базой пользователей и хешами паролей.
import { SignJWT } from "jose";
import { NextResponse } from "next/server";
import { getJwtSecretKey } from "@/libs/auth";
export async function POST(request) {
const body = await request.json();
if (body.username === "admin" && body.password === "admin") {
const token = await new SignJWT({ username: body.username })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("30s")
.sign(getJwtSecretKey());
const response = NextResponse.json(
{ success: true },
{ status: 200, headers: { "content-type": "application/json" } }
);
response.cookies.set({ name: "token", value: token, path: "/" });
return response;
}
return NextResponse.json({ success: false });
}Здесь мы подписываем полезную нагрузку (payload) токена и отправляем токен в cookie. В следующем разделе — функции подписания/проверки.
3. Функции подписи и верификации токена
Создайте src/libs/auth.js и положите туда логику работы с секретом и верификации токена.
import { jwtVerify } from "jose";
export function getJwtSecretKey() {
const secret = process.env.NEXT_PUBLIC_JWT_SECRET_KEY;
if (!secret) {
throw new Error("JWT Secret key is not matched");
}
return new TextEncoder().encode(secret);
}
export async function verifyJwtToken(token) {
try {
const { payload } = await jwtVerify(token, getJwtSecretKey());
return payload;
} catch (error) {
return null;
}
}Добавьте файл .env в корень проекта со значением секретного ключа (здесь показан пример):
NEXT_PUBLIC_JWT_SECRET_KEY=your_secret_keyВажно: не храните секрет в публичном репозитории.
4. Защищённая страница
Создайте src/app/protected/page.js, к которой будет доступ только у авторизованных пользователей:
export default function ProtectedPage() {
return Very protected page
;
}5. Клиентский хук для состояния аутентификации
Хук позволяет компонентам узнавать, авторизован ли пользователь. Создайте src/hooks/useAuth/index.js:
"use client";
import React from "react";
import Cookies from "universal-cookie";
import { verifyJwtToken } from "@/libs/auth";
export function useAuth() {
const [auth, setAuth] = React.useState(null);
const getVerifiedtoken = async () => {
const cookies = new Cookies();
const token = cookies.get("token") ?? null;
const verifiedToken = token ? await verifyJwtToken(token) : null;
setAuth(verifiedToken);
};
React.useEffect(() => {
getVerifiedtoken();
}, []);
return auth;
}Пример использования в app/page.js:
"use client";
import { useAuth } from "@/hooks/useAuth";
import Link from "next/link";
export default function Home() {
const auth = useAuth();
return (
<>
Public Home Page
>
);
}6. Middleware для защиты маршрутов
Создайте src/middleware.js, чтобы перенаправлять неавторизованных пользователей на страницу входа и предотвращать доступ авторизованных к странице входа.
import { NextResponse } from "next/server";
import { verifyJwtToken } from "@/libs/auth";
const AUTH_PAGES = ["/login"];
const isAuthPages = (url) => AUTH_PAGES.some((page) => page.startsWith(url));
export async function middleware(request) {
const { url, nextUrl, cookies } = request;
const { value: token } = cookies.get("token") ?? { value: null };
const hasVerifiedToken = token && (await verifyJwtToken(token));
const isAuthPageRequested = isAuthPages(nextUrl.pathname);
if (isAuthPageRequested) {
if (!hasVerifiedToken) {
const response = NextResponse.next();
response.cookies.delete("token");
return response;
}
const response = NextResponse.redirect(new URL(`/`, url));
return response;
}
if (!hasVerifiedToken) {
const searchParams = new URLSearchParams(nextUrl.searchParams);
searchParams.set("next", nextUrl.pathname);
const response = NextResponse.redirect(new URL(`/login?${searchParams}`, url));
response.cookies.delete("token");
return response;
}
return NextResponse.next();
}
export const config = { matcher: ["/login", "/protected/:path*"] };Middleware — серверный «страж», который решает, пускать ли пользователя дальше.
Критерии приёмки
- Пользователь с корректными данными получает cookie с токеном и переходит на /protected.
- Попытка зайти на /protected без токена перенаправляет на /login с параметром next.
- Просроченный или поддельный токен приводит к удалению cookie и редиректу на /login.
- Страница /login доступна только неавторизованным; авторизованные пользователи перенаправляются на /.
Сценарии тестирования (test cases)
- Успешный вход: POST /api/login с валидным набором логин/пароль возвращает success:true и ставит cookie.
- Неверный логин: возвращается success:false.
- Просроченный токен: middleware удаляет token и перенаправляет на /login.
- Попытка доступа к /protected без токена: redirect на /login?next=/protected.
Чеклисты по ролям
- Разработчик:
- Реализовать проверку пароля через безопасный хеш (bcrypt/argon2).
- Не логировать секретные значения.
- Настроить refresh token, если нужен длительный доступ.
- DevOps:
- Хранить секреты в защищённом хранилище (Vault, Secrets Manager).
- Настроить HTTPS и HSTS.
- Инженер по безопасности:
- Проверить конфигурацию cookie (HttpOnly, Secure, SameSite).
- Провести тесты на XSS и CSRF.
Безопасность и рекомендации по харднингу
- Используйте HttpOnly и Secure cookie для хранения токенов вместо localStorage. Это снижает риск XSS.
- Устанавливайте SameSite=strict или lax в зависимости от сценария, чтобы уменьшить угрозы CSRF.
- Делайте короткие сроки жизни JWT и реализуйте refresh token с возможностью отзыва.
- Хешируйте пароли на сервере (bcrypt/argon2).
- Ограничьте попытки входа (rate limiting, account lockout).
- Подпись токена храните в безопасном месте, не в репозитории.
- Проводите аудит зависимостей и регулярно обновляйте jose и другие пакеты.
Когда подход на JWT может не подойти (контрпримеры)
- Если нужно мгновенно аннулировать доступ по всем сессиям — классические JWT без централизованного хранения сложнее отозвать.
- Для сценариев высокого соответствия (compliance) может потребоваться серверное хранение сессий и журнала доступа.
- Если приложение подразумевает очень частую смену прав доступа, имеет смысл использовать серверные сессии или short lived tokens + introspection.
Примеры curl для отладки
- Попытка входа:
curl -i -X POST http://localhost:3000/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}'- Доступ к защищённому маршруту (cookie нужно передать вручную при отладке):
curl -i http://localhost:3000/protected --cookie "token=YOUR_TOKEN"Приватность и соответствие требованиям (GDPR и похожие)
- Минимизируйте персональные данные в JWT: включайте только необходимую информацию.
- Храните PII отдельно и защищённо; избегайте большого набора персональных данных в токене.
- Если требуется согласие на cookie, показывайте пользователю явную подсказку и давайте выбор.
Ментальные модели и эвристики
- Short lived token + refresh token: уменьшает окно риска при краже токена.
- Публичный ключ/приватный ключ (asymmetric) полезен, если подписывать токены должен один сервис, а проверять — многие.
- «Принцип наименьших привилегий»: токен должен давать ровно те права, которые нужны.
Краткая методология развертывания
- Разработать API для аутентификации и авторизации.
- Выбрать формат токенов и место хранения (cookie vs storage).
- Реализовать подпись и верификацию токенов.
- Настроить middleware для защиты маршрутов.
- Провести нагрузочное и безопасность‑тестирование.
Краткий глоссарий
- JWT: JSON Web Token — компактный способ передачи утверждений в виде подписанного JSON.
- HttpOnly: атрибут cookie, запрещающий доступ через JavaScript.
- Refresh token: токен для получения нового access token, обычно хранится более защищённо.
Итог и рекомендации
- Реализация на JWT даёт гибкость, но накладывает ответственность: управление сроками жизни, отзыв токенов и безопасность хранилища секретов.
- Для большинства приложений начните с короткого срока жизни access token и безопасных cookie.
- Рассмотрите готовые решения (NextAuth) при ограниченных ресурсах на поддержание собственной системы.
Короткое объявление для команды (пример)
Мы добавили базовую JWT‑аутентификацию в Next.js: форма входа, серверная генерация токенов, клиентский хук и middleware для защиты маршрутов. Требуется доработать хранение паролей, настроить refresh token и усилить конфигурацию cookie перед релизом.
Примечание: код в примере служит учебной целью. Перед выпуском в продакшн выполните аудит безопасности и доработайте управление сессиями.