Сессии и аутентификация

Аутентификация — типичное требование веб-приложений. В этом рецепте показана базовая регистрация пользователей и вход в приложение Nuxt.

Введение

Здесь настраивается аутентификация в полноценном Nuxt-приложении с помощью Nuxt Auth Utils — удобные утилиты для сессии на клиенте и сервере.

Модуль хранит данные сессии в защищённых запечатанных cookie, отдельная база для сессий не нужна.

Установка nuxt-auth-utils

Установите модуль nuxt-auth-utils через CLI Nuxt.

Terminal
npx nuxt module add auth-utils
Команда добавит nuxt-auth-utils в зависимости и пропишет модуль в секции modules файла nuxt.config.

Так как nuxt-auth-utils использует запечатанные cookie, сессионные cookie шифруются секретом из переменной окружения NUXT_SESSION_PASSWORD.

Если переменная не задана, в режиме разработки она может быть добавлена в .env автоматически.
.env
NUXT_SESSION_PASSWORD=a-random-password-with-at-least-32-characters
Перед развёртыванием в продакшен добавьте эту переменную в окружение продакшена.

API-маршрут входа

В этом рецепте создаётся простой API-маршрут для входа по статическим данным.

Создайте маршрут /api/login, принимающий POST с email и паролем в теле запроса.

server/api/login.post.ts
import { z } from 'zod'

const bodySchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
})

export default defineEventHandler(async (event) => {
  const { email, password } = await readValidatedBody(event, bodySchema.parse)

  if (email === 'admin@admin.com' && password === 'iamtheadmin') {
    // сохраняем сессию пользователя в cookie
    // этот серверный хелпер подхватывается автоимпортом модуля auth-utils
    await setUserSession(event, {
      user: {
        name: 'John Doe',
      },
    })
    return {}
  }
  throw createError({
    statusCode: 401,
    message: 'Неверные учётные данные',
  })
})
Установите зависимость zod в проект (например, npm i zod).
Подробнее о серверном хелпере setUserSession из nuxt-auth-utils.

Страница входа

Модуль предоставляет Vue-композабл для проверки, авторизован ли пользователь:

<script setup>
const { loggedIn, session, user, clear, fetch } = useUserSession()
</script>

Создайте страницу входа с формой, отправляющей данные на маршрут /api/login.

pages/login.vue
<script setup lang="ts">
const { loggedIn, user, fetch: refreshSession } = useUserSession()
const credentials = reactive({
  email: '',
  password: '',
})
async function login () {
  try {
    await $fetch('/api/login', {
      method: 'POST',
      body: credentials,
    })

    // обновляем сессию на клиенте и переходим на главную
    await refreshSession()
    await navigateTo('/')
  } catch {
    alert('Неверные учётные данные')
  }
}
</script>

<template>
  <form @submit.prevent="login">
    <input
      v-model="credentials.email"
      type="email"
      placeholder="Эл. почта"
    >
    <input
      v-model="credentials.password"
      type="password"
      placeholder="Пароль"
    >
    <button type="submit">
      Войти
    </button>
  </form>
</template>

Защита API-маршрутов

Защита серверных маршрутов нужна, чтобы данные были в безопасности. Клиентский middleware удобен пользователю, но без проверки на сервере данные всё равно можно запросить. Любые маршруты с чувствительными данными должны отвечать 401, если пользователь не вошёл.

В auth-utils есть requireUserSession — гарантирует, что у запроса есть валидная сессия.

Пример маршрута /api/user/stats, доступного только авторизованным:

server/api/user/stats.get.ts
export default defineEventHandler(async (event) => {
  // убеждаемся, что пользователь авторизован
  // при отсутствии валидной сессии будет выброшена ошибка 401
  const { user } = await requireUserSession(event)

  // TODO: получить статистику по пользователю

  return {}
})

Защита маршрутов приложения

Серверный маршрут защищает данные, но без дополнительных мер неавторизованный пользователь может открыть, например, /users и увидеть странное поведение. Стоит добавить клиентский middleware, чтобы на клиенте перенаправлять на страницу входа.

В nuxt-auth-utils для этого есть композабл useUserSession: проверяем loggedIn и при необходимости редиректим.

Создайте middleware в каталоге /middleware. В отличие от сервера, клиентский middleware не применяется ко всем маршрутам автоматически — его нужно явно указать.

middleware/authenticated.ts
export default defineNuxtRouteMiddleware(() => {
  const { loggedIn } = useUserSession()

  // если не авторизован — на страницу входа
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
})

Главная страница

С middleware можно защитить домашнюю страницу с информацией о пользователе: если не авторизован — редирект на /login.

Подключите middleware через definePageMeta.

pages/index.vue
<script setup lang="ts">
definePageMeta({
  middleware: ['authenticated'],
})

const { user, clear: clearSession } = useUserSession()

async function logout () {
  await clearSession()
  await navigateTo('/login')
}
</script>

<template>
  <div>
    <h1>Добро пожаловать, {{ user.name }}</h1>
    <button @click="logout">
      Выйти
    </button>
  </div>
</template>

Добавлена кнопка выхода: очищает сессию и ведёт на /login.

Заключение

Настроены базовая аутентификация и сессии, защищены чувствительные маршруты на сервере и клиенте — доступ только у вошедших пользователей.

Дальше можно:

Полный пример приложения с OAuth, базой и CRUD — в открытом репозитории atidone.