17. Аутентификация
🔐 Аутентификация в Nuxt 3
Заголовок раздела «🔐 Аутентификация в Nuxt 3»Аутентификация — одна из ключевых задач любого веб-приложения. Nuxt 3 предлагает несколько подходов: от готовых модулей до реализации с нуля через composables и server routes.
📦 nuxt-auth-utils
Заголовок раздела «📦 nuxt-auth-utils»Официальный минималистичный модуль от команды Nuxt для управления сессиями:
npx nuxi module add auth-utilsexport default defineNuxtConfig({ modules: ['nuxt-auth-utils']})NUXT_SESSION_PASSWORD=at-least-32-chars-long-secret-key!!Настройка OAuth провайдеров
Заголовок раздела «Настройка OAuth провайдеров»export default defineOAuthGitHubEventHandler({ config: { clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET }, async onSuccess(event, { user, tokens }) { await setUserSession(event, { user: { id: user.id, name: user.name, email: user.email, avatar: user.avatar_url, provider: 'github' } }) return sendRedirect(event, '/dashboard') }, onError(event, error) { console.error('GitHub OAuth error:', error) return sendRedirect(event, '/login?error=github') }})export default defineOAuthGoogleEventHandler({ config: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, scope: ['openid', 'email', 'profile'] }, async onSuccess(event, { user }) { await setUserSession(event, { user: { id: user.sub, name: user.name, email: user.email, avatar: user.picture, provider: 'google' } }) return sendRedirect(event, '/dashboard') }})🔑 useUserSession()
Заголовок раздела «🔑 useUserSession()»<script setup lang="ts">const { loggedIn, user, session, fetch, clear } = useUserSession()
// loggedIn — boolean, авторизован ли пользователь// user — данные пользователя// session — полная сессия// fetch — перезагрузить сессию// clear — выйти</script>
<template> <div v-if="loggedIn"> <img :src="user.avatar" /> <p>Привет, {{ user.name }}!</p> <button @click="clear">Выйти</button> </div> <div v-else> <NuxtLink to="/login">Войти</NuxtLink> </div></template>🏗️ @sidebase/nuxt-auth
Заголовок раздела «🏗️ @sidebase/nuxt-auth»Мощный модуль с поддержкой NextAuth.js / Auth.js:
npx nuxi module add @sidebase/nuxt-authexport default defineNuxtConfig({ modules: ['@sidebase/nuxt-auth'], auth: { provider: { type: 'authjs' }, globalAppMiddleware: { isEnabled: true } }})Настройка Auth.js
Заголовок раздела «Настройка Auth.js»import { NuxtAuthHandler } from '#auth'import GithubProvider from 'next-auth/providers/github'import GoogleProvider from 'next-auth/providers/google'import CredentialsProvider from 'next-auth/providers/credentials'import { PrismaAdapter } from '@auth/prisma-adapter'import { prisma } from '~/server/utils/prisma'
export default NuxtAuthHandler({ adapter: PrismaAdapter(prisma), secret: process.env.NEXTAUTH_SECRET, providers: [ GithubProvider({ clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET }), GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET }), CredentialsProvider({ name: 'credentials', credentials: { email: { label: 'Email', type: 'email' }, password: { label: 'Пароль', type: 'password' } }, async authorize(credentials) { const user = await prisma.user.findUnique({ where: { email: credentials.email } }) if (user && await bcrypt.compare(credentials.password, user.password)) { return { id: user.id, email: user.email, name: user.name } } return null } }) ], callbacks: { jwt({ token, user }) { if (user) { token.id = user.id token.role = user.role } return token }, session({ session, token }) { session.user.id = token.id as string session.user.role = token.role as string return session } }, pages: { signIn: '/auth/login', error: '/auth/error' }})🪝 useAuth()
Заголовок раздела «🪝 useAuth()»<script setup lang="ts">const { status, data, signIn, signOut, getCsrfToken } = useAuth()
// status: 'authenticated' | 'unauthenticated' | 'loading'// data: сессия с данными пользователя
const handleSignIn = () => { signIn('github', { callbackUrl: '/dashboard' })}
const handleSignOut = () => { signOut({ callbackUrl: '/' })}</script>
<template> <div> <div v-if="status === 'loading'">Загрузка...</div> <div v-else-if="status === 'authenticated'"> <p>{{ data.user.name }}</p> <button @click="handleSignOut">Выйти</button> </div> <div v-else> <button @click="handleSignIn">Войти через GitHub</button> </div> </div></template>🛡️ Защита маршрутов через middleware
Заголовок раздела «🛡️ Защита маршрутов через middleware»export default defineNuxtRouteMiddleware((to) => { const { status } = useAuth()
if (status.value === 'unauthenticated') { return navigateTo({ path: '/auth/login', query: { redirect: to.fullPath } }) }})export default defineNuxtRouteMiddleware((to) => { const { data } = useAuth()
if (data.value?.user?.role !== 'admin') { return navigateTo('/403') }})<script setup>definePageMeta({ middleware: ['auth', 'admin']})</script>🎟️ JWT аутентификация вручную
Заголовок раздела «🎟️ JWT аутентификация вручную»Реализация JWT без внешних модулей:
import jwt from 'jsonwebtoken'
const SECRET = process.env.JWT_SECRET!
export function signToken(payload: object) { return jwt.sign(payload, SECRET, { expiresIn: '15m' })}
export function signRefreshToken(payload: object) { return jwt.sign(payload, SECRET + '_refresh', { expiresIn: '7d' })}
export function verifyToken(token: string) { return jwt.verify(token, SECRET) as jwt.JwtPayload}
export function verifyRefreshToken(token: string) { return jwt.verify(token, SECRET + '_refresh') as jwt.JwtPayload}export default defineEventHandler(async (event) => { const { email, password } = await readBody(event)
const user = await prisma.user.findUnique({ where: { email } }) if (!user || !await bcrypt.compare(password, user.password)) { throw createError({ statusCode: 401, message: 'Неверные учётные данные' }) }
const accessToken = signToken({ id: user.id, email: user.email, role: user.role }) const refreshToken = signRefreshToken({ id: user.id })
// Сохраняем refresh token в httpOnly cookie setCookie(event, 'refresh_token', refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 60 * 60 * 24 * 7 // 7 дней })
return { accessToken, user: { id: user.id, name: user.name, email: user.email } }})🔄 Refresh Tokens
Заголовок раздела «🔄 Refresh Tokens»export default defineEventHandler(async (event) => { const refreshToken = getCookie(event, 'refresh_token')
if (!refreshToken) { throw createError({ statusCode: 401, message: 'Refresh token отсутствует' }) }
try { const payload = verifyRefreshToken(refreshToken) const user = await prisma.user.findUnique({ where: { id: payload.id } })
if (!user) { throw createError({ statusCode: 401, message: 'Пользователь не найден' }) }
const newAccessToken = signToken({ id: user.id, email: user.email, role: user.role })
return { accessToken: newAccessToken } } catch { deleteCookie(event, 'refresh_token') throw createError({ statusCode: 401, message: 'Недействительный refresh token' }) }})📋 Сравнение подходов
Заголовок раздела «📋 Сравнение подходов»| Подход | Сложность | Гибкость | Когда использовать |
|---|---|---|---|
nuxt-auth-utils | ⭐ Низкая | 🔧 Средняя | OAuth, быстрый старт |
@sidebase/nuxt-auth | ⭐⭐ Средняя | 🔧🔧 Высокая | Комплексные проекты |
| JWT вручную | ⭐⭐⭐ Высокая | 🔧🔧🔧 Полная | Кастомная логика |
| Lucia Auth | ⭐⭐ Средняя | 🔧🔧 Высокая | Типобезопасность |