Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Аутентификация в Nuxt 3
nuxt_authentication

Аутентификация в Nuxt 3

JWT, httpOnly cookies, серверные middleware для защиты маршрутов, useAuth composable и refresh token flow

Авторизация в Nuxt 3

Авторизация в Nuxt 3 — это не просто хранение токена в localStorage. SSR требует синхронизации состояния между сервером и клиентом, а безопасность требует httpOnly cookies вместо доступного из JS хранилища.

#Стратегии авторизации в Nuxt: cookie-based vs JWT

Два основных подхода и их компромиссы.

Cookie-based сессии: сервер создаёт сессию, отправляет cookie с session_id. При каждом запросе сервер читает cookie и загружает данные сессии из хранилища. Плюсы: простота инвалидации (удали сессию на сервере), нет проблем с SSR. Минусы: требует хранилища сессий (Redis), не масштабируется без sticky sessions или централизованного хранилища.

JWT токены: сервер выдаёт подписанный токен, клиент хранит его и отправляет в каждом запросе. Сервер верифицирует подпись без обращения к БД. Плюсы: stateless, масштабируется. Минусы: трудная инвалидация (нужен blacklist), нужна стратегия refresh токенов.

Для Nuxt оптимальна гибридная схема: JWT токен в httpOnly cookie. Cookie отправляется автоматически с каждым запросом, недоступен из JavaScript (защита от XSS), а на сервере Nuxt может читать его для SSR-запросов.

#nuxt-auth-utils: установка и базовое использование

nuxt-auth-utils — минималистичный модуль для управления сессией в Nuxt через зашифрованные cookies:

npm install nuxt-auth-utils

Добавьте в nuxt.config.ts:

export default defineNuxtConfig({ modules: ['nuxt-auth-utils'], runtimeConfig: { session: { password: process.env.NUXT_SESSION_PASSWORD // Минимум 32 символа } } })

Обязательно задайте NUXT_SESSION_PASSWORD в .env — это ключ шифрования cookie:

NUXT_SESSION_PASSWORD=my-super-secret-key-at-least-32-chars-long

#Server-side: эндпоинт логина

Весь процесс авторизации — на сервере. Клиент отправляет учётные данные, сервер проверяет и создаёт сессию:

// server/api/auth/login.post.ts import { z } from 'zod' const loginSchema = z.object({ email: z.string().email(), password: z.string().min(8) }) export default defineEventHandler(async (event) => { // Читаем и валидируем тело запроса const body = await readValidatedBody(event, loginSchema.parse) // Проверяем учётные данные в БД (используйте bcrypt для сравнения паролей) const user = await db.users.findByEmail(body.email) if (!user || !await verifyPassword(body.password, user.passwordHash)) { throw createError({ statusCode: 401, statusMessage: 'Неверный email или пароль' }) } if (!user.isActive) { throw createError({ statusCode: 403, statusMessage: 'Аккаунт заблокирован' }) } // Создаём сессию через nuxt-auth-utils await setUserSession(event, { user: { id: user.id, email: user.email, name: user.name, role: user.role }, loggedInAt: new Date().toISOString() }) return { success: true, user: { id: user.id, name: user.name } } })

#Server-side: эндпоинт выхода

// server/api/auth/logout.post.ts export default defineEventHandler(async (event) => { // clearUserSession удаляет cookie и инвалидирует сессию await clearUserSession(event) return { success: true } })

#Composable useUserSession на клиенте

useUserSession() — основной composable для работы с сессией. Автоматически синхронизируется с сервером:

<!-- components/UserMenu.vue --> <script setup lang="ts"> const { user, loggedIn, fetch: refreshSession } = useUserSession() async function handleLogout() { // Вызываем эндпоинт логаута await $fetch('/api/auth/logout', { method: 'POST' }) // Обновляем состояние сессии на клиенте await refreshSession() // Редирект на главную await navigateTo('/') } </script> <template> <div v-if="loggedIn"> <span>{{ user?.name }}</span> <button @click="handleLogout">Выйти</button> </div> <NuxtLink v-else to="/login">Войти</NuxtLink> </template>

#Защита маршрутов через Middleware

Route middleware — правильное место для проверки авторизации. Middleware выполняется до рендеринга страницы как на сервере, так и на клиенте:

// middleware/auth.ts — применяется вручную через definePageMeta export default defineNuxtRouteMiddleware((to) => { const { loggedIn } = useUserSession() if (!loggedIn.value) { // Сохраняем целевую страницу для редиректа после логина return navigateTo({ path: '/login', query: { redirect: to.fullPath } }) } })

Применение к конкретным страницам:

<!-- pages/dashboard.vue --> <script setup lang="ts"> // Применяем middleware auth к этой странице definePageMeta({ middleware: ['auth'] }) </script>

#Глобальный middleware: auth.global.ts

Если большинство страниц требуют авторизации, удобнее использовать глобальный middleware с белым списком открытых страниц:

// middleware/auth.global.ts const publicRoutes = ['/', '/login', '/register', '/about', '/blog'] export default defineNuxtRouteMiddleware((to) => { const { loggedIn } = useUserSession() // Проверяем что маршрут не публичный const isPublic = publicRoutes.some(route => to.path === route || to.path.startsWith('/blog/') ) if (!isPublic && !loggedIn.value) { return navigateTo(`/login?redirect=${encodeURIComponent(to.fullPath)}`) } })

#Защита server routes

API эндпоинты тоже нужно защищать. requireUserSession бросает 401 если сессия отсутствует:

// server/api/profile.get.ts export default defineEventHandler(async (event) => { // Требует активной сессии — иначе 401 const { user } = await requireUserSession(event) // user типизирован из вашей сессии const profile = await db.users.findById(user.id) return profile })

Для сложных проверок прав:

// server/api/admin/users.get.ts export default defineEventHandler(async (event) => { const { user } = await requireUserSession(event) if (user.role !== 'admin') { throw createError({ statusCode: 403, statusMessage: 'Недостаточно прав' }) } return await db.users.findAll() })

#Pinia store для user state

Для сложных приложений удобно хранить данные пользователя в Pinia store, синхронизируя с сессией:

// stores/auth.ts export const useAuthStore = defineStore('auth', () => { const { user: sessionUser, loggedIn } = useUserSession() // Дополнительные вычисленные данные const isAdmin = computed(() => sessionUser.value?.role === 'admin') const displayName = computed(() => sessionUser.value?.name || sessionUser.value?.email || 'Гость' ) async function login(email: string, password: string) { const result = await $fetch('/api/auth/login', { method: 'POST', body: { email, password } }) // useUserSession автоматически обновится при следующей проверке await navigateTo('/dashboard') return result } async function logout() { await $fetch('/api/auth/logout', { method: 'POST' }) await navigateTo('/') } return { loggedIn, isAdmin, displayName, login, logout } })

#OAuth провайдеры: GitHub и Google

nuxt-auth-utils поддерживает OAuth "из коробки":

// server/api/auth/github.get.ts export default defineOAuthGitHubEventHandler({ config: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, scope: ['user:email'] }, async onSuccess(event, { user, tokens }) { // user — данные от GitHub API // Ищем или создаём пользователя в нашей БД let dbUser = await db.users.findByGithubId(user.id) if (!dbUser) { dbUser = await db.users.create({ githubId: user.id, email: user.email, name: user.name, avatarUrl: user.avatar_url }) } // Устанавливаем сессию await setUserSession(event, { user: { id: dbUser.id, email: dbUser.email, name: dbUser.name, role: dbUser.role } }) return sendRedirect(event, '/dashboard') }, async onError(event, error) { console.error('GitHub OAuth error:', error) return sendRedirect(event, '/login?error=oauth_failed') } })

Добавьте кнопку на страницу логина:

<template> <a href="/api/auth/github" class="oauth-btn"> Войти через GitHub </a> </template>

#RBAC: ролевой контроль доступа

Роли хранятся в сессии и проверяются в middleware и server routes:

// middleware/admin.ts export default defineNuxtRouteMiddleware(() => { const { user, loggedIn } = useUserSession() if (!loggedIn.value) { return navigateTo('/login') } if (user.value?.role !== 'admin') { throw createError({ statusCode: 403, statusMessage: 'Доступ запрещён' }) } })

Для гранулярного контроля — composable с проверкой прав:

// composables/usePermissions.ts export function usePermissions() { const { user } = useUserSession() const can = (permission: string): boolean => { const role = user.value?.role const permissions: Record<string, string[]> = { admin: ['read', 'write', 'delete', 'manage_users'], moderator: ['read', 'write'], user: ['read'] } return permissions[role ?? 'user']?.includes(permission) ?? false } return { can } }

#Типичные ошибки и безопасность

Ошибка 1: хранить токен в localStorage. Он доступен любому JS-коду — XSS атака полностью компрометирует авторизацию. Используйте httpOnly cookies.

Ошибка 2: не проверять авторизацию на сервере. Middleware на клиенте — это UX, не безопасность. Клиент может его обойти. Всегда проверяйте в server routes.

Ошибка 3: race condition при инициализации. Если несколько запросов идут до того как сессия загружена, возникают конкурентные проблемы. Используйте await при загрузке сессии в плагинах.

Ошибка 4: hydration mismatch. Если сервер рендерит "авторизованный" контент, а клиент видит "неавторизованный" (или наоборот) — возникает hydration error. Убедитесь что useUserSession() одинаково работает на обеих сторонах.

Ошибка 5: отсутствие CSRF защиты. nuxt-auth-utils использует SameSite=Lax для cookies — это базовая защита. Для критических операций добавьте CSRF токен.

Далее: Интернационализация (i18n)