Гид по технологиям

Аутентификация в Next.js с JWT: пошаговое руководство

7 min read Разработка Обновлено 07 Jan 2026
Next.js: аутентификация с JWT и защита роутов
Next.js: аутентификация с JWT и защита роутов

Мужчина за рабочим столом печатает на ноутбуке с кодом на экране.

О чём этот материал

  • Основной подход: выдавать краткоживущий access token (JWT) при логине и хранить его в защищённой куки.
  • Проверять токен на сервере (middleware и API-эндпоинты) с помощью библиотеки jose.
  • Обеспечить UX: перенаправления, состояние клиента и поддержка защищённых маршрутов.

Варианты, которые обсуждаются: NextAuth (готовое решение), собственная реализация JWT, использование refresh-токенов, хранение токенов в куках против localStorage.

Ключевые термины — одно предложение каждая

  • JWT: JSON Web Token — компактный самодостаточный токен с полезной нагрузкой и подписью.
  • Access token: короткоживущий JWT, подтверждающий права доступа.
  • Refresh token: долгоживущий токен для получения новых access token.
  • Cookie (куки): механизм хранения данных в браузере, может быть защищён флагами HttpOnly и Secure.

Предварительные требования

  • Node.js и npm установлены.
  • Проект на Next.js 13 с директориeй app.
  • Понимание основ fetch API, React hooks и работы с куками.

Установка и инициализация проекта

Чтобы начать, создайте проект Next.js 13 (app directory):

npx create-next-app@latest next-auth-jwt --experimental-app

Установите зависимости:

npm install jose universal-cookie

Jose — утилиты для работы с JWT. Universal-cookie — удобная библиотека для работы с куками как на клиенте, так и на сервере.

Структура папок проекта Next.js 13 в VS Code.

Код примера доступен в репозитории (упомянуто в оригинале).

UI: форма входа

Создайте страницу логина в src/app/login/page.js со следующим компонентом (клиентский компонент):

"use client";  
import { useRouter } from "next/navigation";  
  
export default function LoginPage() {  
  return (  
    
                         
  ); }

Этот компонент рендерит простую форму. Директива “use client” объявляет, что код выполняется в браузере.

Добавим обработчик отправки формы внутрь компонента:

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 }),  
    });  
    const { success } = await res.json();  
    if (success) {      
      router.push("/protected");  
      router.refresh();  
    } else {  
      alert("Login failed");  
    }  
 };

Этот хендлер отправляет POST на /api/login, ожидая, что сервер установит куку с JWT при удачном входе.

API-эндпоинт логина — генерация JWT

Создайте src/app/api/login/route.js и вставьте следующий код:

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 });  
}

Комментарий: Здесь используется простая заглушка: логин успешен только для admin/admin. При успешном логине сервер подписывает JWT и возвращает его в куке “token”.

Важно: у вас в продакшене кука должна иметь флаги HttpOnly, Secure и SameSite, чтобы повысить безопасность (см. раздел “Усиление безопасности”).

Проверка JWT

Создайте файл 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

Примечание: в продакшне секрет должен храниться в безопасном месте (секреты окружения, vault) и не попадать в клиентский бандл.

Защищённая страница

Создайте src/app/protected/page.js:

export default function ProtectedPage() {  
    return 

Very protected page

;   }

Вместо простого заголовка разместите реальное защищённое содержимое или SSR-логики, проверяющие права доступа.

Клиентский хук для состояния аутентификации

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 = await verifyJwtToken(token);  
    setAuth(verifiedToken);  
  };  
  React.useEffect(() => {  
    getVerifiedtoken();  
  }, []);  
  return auth;  
}

Хук читает куку и вызывает verifyJwtToken. Это даёт клиентскому UI информацию о текущем пользователе.

В app/page.js пример использования:

"use client" ;  
  
import { useAuth } from "@/hooks/useAuth";  
import Link from "next/link";  
export defaultfunction Home() {  
  const auth =  useAuth();  
  return <>  
           

Public Home Page

           
                         
   }

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 проверяет токен и перенаправляет неаутентифицированных пользователей на страницу логина, сохраняя путь в параметре next.


Усиление безопасности (важно)

Ниже — практические рекомендации, которые помогут снизить риски при использовании JWT в браузере.

  • Хранение токенов: предпочтительнее — HttpOnly куки с флагами Secure и SameSite=Strict или Lax. Избегайте хранения access token в localStorage, если важна защита от XSS.
  • Срок жизни access token: делайте коротким (например, минуты/несколько часов). Для долгоживущих сеансов используйте refresh token, хранимый в безопасной HttpOnly куке на серверной стороне.
  • Подпись и алгоритм: используйте сильный секрет и алгоритмы (HS256 / RS256 в зависимости от архитектуры). Для RS* храните приватный ключ отдельно.
  • Защита от CSRF: при использовании куков добавьте CSRF-токен либо используйте SameSite куки в комбинации с проверками Origin/Referer.
  • Ротация и отзыв: реализуйте возможность отзыва токена (черный список) для критичных сценариев (утечка, принудительный выход).
  • Логи и мониторинг: логируйте попытки аутентификации и аномалии (много неудачных входов, необычные IP).

Important: в примере выше кука устанавливается без флагов безопасности — настройте их в продакшн.

Альтернативные подходы

  • NextAuth: кастомная и более функциональная библиотека для Next.js, поддерживает провайдеры OAuth, сессии, JWT, адаптеры баз данных. Хороша, если нужен быстрый релиз.
  • Сессии на сервере: хранить сессии в базе/Redis и обслуживать по session cookie — удобно для мгновенного отзыва прав.
  • OAuth2 / OpenID Connect: для интеграции с внешними провайдерами идентификации.

Когда собственный JWT оправдан: простые API, микросервисы, необходимость контролировать payload токена. Когда не оправдан: если нужен быстрый старт с множеством провайдеров — лучше NextAuth.

Модель принятия решений (мнемоника)

  • Если нужна мгновенная отзывчивость прав => сессии на сервере.
  • Если нужна независимость сервисов и самодостаточные токены => JWT.
  • Если важна простота интеграции с OAuth-провайдерами => NextAuth.

Тесты и критерии приёмки

Критерии приёмки:

  • Успешный вход под валидными учётными данными приводит к установке куки “token” и редиректу на защищённую страницу.
  • Без токена доступ к /protected приводит к редиректу на /login с параметром next.
  • Время жизни токена соблюдается (после истечения — доступ запрещён).
  • Попытка доступа к /login будучи аутентифицированным — перенаправляет на главную.

Минимальные тесты (acceptance):

  • TC1: Валидный логин -> кука установлена, /protected доступна.
  • TC2: Невалидный логин -> сообщение об ошибке, кука не установлена.
  • TC3: Удаление куки -> доступы к защищённым ресурсам запрещены.
  • TC4: Попытка CSRF -> отрабатывает защита (в зависимости от выбранной схемы).

Сценарии отказа и откат

Когда может провалиться:

  • Секрет JWT изменён или не совпадает с тем, что использовался для подписи — все токены станут недействительны.
  • Истечение срока токена без механизма refresh -> UX-проблемы.
  • Неправильно настроенные куки (нет HttpOnly/Secure/SameSite) -> повышенный риск XSS/CSRF.

Откат: при смене секретов — реализуйте стратегию ротации ключей: сначала принимайте старые и новые ключи, затем постепенно откатывайте старые.


Рекомендации по GDPR и приватности

  • Не храните в JWT чувствительные персональные данные (PII) в явном виде. Если нужно — храните только идентификатор пользователя и извлекайте данные с сервера по запросу.
  • Сроки хранения: объясните пользователю и храните только необходимое время.
  • Предоставьте пользователю возможность удалить аккаунт и связанные с ним данные — это может потребовать удаления/отзыва токенов.

Чеклист по ролям

Для разработчика фронтенда:

  • Проверить, что кука читается корректно в useAuth.
  • Обработать состояние проседания токена (loading, expired).

Для бекенд-разработчика:

  • Обеспечить подпись токенов и безопасное хранение секретов.
  • Настроить флаги куки и CSRF-защиту или реализовать защитные заголовки.

Для DevOps:

  • Хранить секреты в безопасном хранилище.
  • Настроить HTTPS для домена и Secure-флаг куки.

Шаблон проверки безопасности (коротко)

  • HTTPS включён: да/нет
  • HttpOnly: да/нет
  • Secure: да/нет
  • SameSite: Lax/Strict
  • Срок жизни access token: установлен
  • Реализован refresh token: да/нет

Отладка — основные шаги

  1. Проверьте, пришла ли кука в ответе сервера (Network → Set-Cookie).
  2. Убедитесь, что кука содержит ожидаемый JWT и флаги.
  3. Попробуйте вручную распарсить/проверить токен с тем же секретом (локально) с помощью jose.
  4. Логи: вывод ошибок jwtVerify для понимания причины (expired, signature invalid и т.д.).

Краткая таблица “Когда это работает/когда нет”

  • Работает: одностраничные приложения с простым API, требующим аутентификации.
  • Неидеально: приложения, где нужно мгновенно отзывать доступы без хранения списков отзыва на сервере (в этом случае лучше сессии).

Глоссарий (1 строка каждый)

  • Access token: короткоживущий токен для авторизации запросов.
  • Refresh token: долгоживущий токен для обновления access token.
  • HttpOnly: атрибут куки, недоступный через JavaScript.
  • SameSite: атрибут куки, ограничивающий отправку в кросс-сайтовых запросах.

Краткое резюме

  • Собственная JWT-авторизация в Next.js даёт контроль, но требует аккуратной настройки безопасности.
  • Используйте HttpOnly и Secure куки, короткие access token и опциональные refresh token’ы.
  • Рассмотрите NextAuth или серверные сессии, если нужны готовые сценарии управления сессиями и авторизацией.

Дополнительные материалы: реализация refresh token’ов, ротация ключей, интеграция с внешними провайдерами — логичное продолжение этого руководства.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство