Auth.js

Auth.js - известная библиотека для аутентификации, используемая в различных JS-фреймворках. Благодаря Auth.js мы получаем преимущество в виде снижения сложности разработки аутентификации. Мы можем иметь доступ к различным провайдерам аутентификации, таким как GitHub, Google, Facebook и др. Кроме того, она может быть интегрирована в различные фреймворки, включая Qwik.

Auth.js предлагает несколько функций, которые повышают простоту, производительность, гибкость и разнообразие провайдеров. Вот основные возможности Auth.js:

  • Провайдеры: Auth.js поддерживает множество провайдеров, упрощая процесс аутентификации в наших приложениях (например, Github, Google, Facebook, Twitter). Она также предлагает услуги Single Sign-On (SSO), а также традиционную аутентификацию.
  • Управление: Auth.js помогает нам сконцентрироваться на бизнес-логике. Она управляет токенами, их хранением и автоматическим обновлением.
  • Конфигурация: Настройка Auth.js не представляет собой ничего сложного. Он отличается простотой установки, обработкой ошибок, наличием пользовательских форм для входа и регистрации, а также удобной интеграцией с провайдерами.
  • Интеграции: Auth.js легко интегрируется с JS-фреймворками, чему способствует исчерпывающая документация, предоставляющая чёткое руководство к действию.
  • Безопасность: Несмотря на то что Auth.js удобен для разработчиков, необходимо осознавать сложность, лежащую в его основе и обеспечивающую высокий уровень безопасности наших данных.

Имейте в виду, что библиотека Auth.js все ещё находится на стадии pre-1.0 и может иметь ошибки.

Установка

Вы можете легко добавить Auth.js с помощью следующей команды Qwik:

npm run qwik add auth

Эта команда добавит новые пакеты:

  • @auth/core
  • @builder.io/qwik-auth

и создаст новый файл с именем plugin@auth.ts с примером конфигурации.

Необходимые ручные действия

После установки пакета auth с помощью npm run qwik add auth пакет @auth/core необходимо добавить в настройки оптимизации зависимостей в файле vite.config.js:

vite.config.js
export default defineConfig(() => {
  return {
    plugins: [qwikCity(), qwikVite(), tsconfigPaths()],
    dev: {
      headers: {
        'Cache-Control': 'public, max-age=0',
      },
    },
    preview: {
      headers: {
        'Cache-Control': 'public, max-age=600',
      },
    },
    optimizeDeps: {
      include: [ "@auth/core" ]
    }
  };
});

Qwik API

useAuthSession

routeLoader$, который возвращает объект сессии или пустой объект, если сессия отсутствует. Содержимое возвращаемого объекта сессии настраивается с помощью функции обратного вызова сессии. Данные сессии также можно получить с помощью REST API session.

import { component$ } from '@builder.io/qwik';
import { useAuthSession } from '~/routes/plugin@auth';
 
export default component$(() => {
  const session = useAuthSession();
  return <p>{session.value?.user?.email}</p>;
});

useAuthSignin

routeAction$ используется для инициирования потока регистрации или отправки пользователя на страницу регистрации со списком всех возможных провайдеров. Токен CSRF обрабатывается внутренне при входе в систему с помощью useAuthSignin.

Параметры

  • providerId: Необязательный строковый параметр с именем провайдера. Если установлен, то инициирует запрос авторизации к поставщику идентификационных данных. При отсутствии этого параметра происходит перенаправление на встроенную/небрендированную страницу регистрации.
  • options: Необязательный объект опций.
    • callbackUrl: Необязательная строка, указывающая, на какой URL-адрес будет перенаправлен пользователь после входа в систему. По умолчанию используется URL страницы, с которой инициируется вход.
  • authorizationParams: Необязательный объект дополнительных параметров, передаваемых конечной точке /authorize. Некоторые идеи см. в спецификации Authorization Request OIDC.

ПРИМЕЧАНИЕ: Вы также можете установить authorizationParams через конфигурацию provider.authorizationParams.

Пример использования useAuthSignin с компонентом <Form> и необязательными providerId и options.callbackUrl:

import { component$ } from '@builder.io/qwik';
import { Form } from '@builder.io/qwik-city';
import { useAuthSignin } from '~/routes/plugin@auth';
 
export default component$(() => {
  const signIn = useAuthSignin();
  return (
    <Form action={signIn}>
      <input type="hidden" name="providerId" value="github" />
      <input type="hidden" name="options.callbackUrl" value="http://qwik-auth-example.com/dashboard" />
      <button>Войти</button>
    </Form>
  );
});

Пример программного использования useAuthSignin с опциональными providerId и options.callbackUrl:

import { component$ } from '@builder.io/qwik';
import { useAuthSignin } from '~/routes/plugin@auth';
 
export default component$(() => {
  const signIn = useAuthSignin();
  return (
    <button onClick$={() => signIn.submit({ providerId: 'github', options: { callbackUrl: 'http://qwik-auth-example.com/dashboard' } })}>Войти</button>
  );
});

useAuthSignout

routeAction$, используемый для инициирования процесса выхода из системы. Сессия пользователя будет аннулирована/удалена из кук/базы данных, в зависимости от выбранного вами способа хранения сессий.

Параметры

  • callbackUrl: Необязательная строка, указывающая, на какой URL-адрес будет перенаправлен пользователь после выхода из системы. По умолчанию используется URL страницы, с которой инициируется вход.

'callbackUrl' должен считаться действительным обработчиком функции обратного вызова перенаправления. По умолчанию он требует, чтобы URL был абсолютным URL с тем же именем хоста, или вы можете указать относительный URL, начинающийся со слэша. Если он не совпадает, произойдёт перенаправление на домашнюю страницу. Вы можете определить свою собственную функцию обратного вызова перенаправления, чтобы разрешить другие URL-адреса.

Пример использования useAuthSignout с компонентом <Form> и необязательным callbackUrl:

import { component$ } from '@builder.io/qwik';
import { Form } from '@builder.io/qwik-city';
import { useAuthSignout } from '~/routes/plugin@auth';
 
export default component$(() => {
  const signOut = useAuthSignout();
  return (
    <Form action={signOut}>
      <input type="hidden" name="callbackUrl" value="/signedout" />
      <button>Выйти</button>
    </Form>
  );
});

Пример программного использования useAuthSignout с опциональным callbackUrl:

import { component$ } from '@builder.io/qwik';
import { useAuthSignout } from '~/routes/plugin@auth';
 
export default component$(() => {
  const signOut = useAuthSignout();
  return <button onClick$={() => signOut.submit({ callbackUrl: '/signedout' })}>Выйти</button>;
});

REST API

Доступны все те же REST API, что и в Auth.js.

signin

GET /api/auth/signin

Отображает встроенную/небрендированную страницу входа в систему.

POST /api/auth/signin/:provider

Запускает процесс регистрации для конкретного поставщика услуг. В случае OAuth-провайдера вызов этой конечной точки инициирует запрос авторизации к вашему поставщику идентификационных данных. Эта конечная точка также используется внутренним действием useAuthSignin.

callback

GET/POST /api/auth/callback/:provider

signout

GET /api/auth/signout

Отображает встроенную/небрендированную страницу выхода из системы.

POST /api/auth/signout

Обработка выхода пользователя из системы - это POST-представление для предотвращения того, чтобы вредоносные ссылки вызывали выход пользователя из системы без его согласия. Сессия пользователя будет аннулирована/удалена из кук/базы данных, в зависимости от выбранного вами способа хранения сессий. Эта конечная точка также используется внутренним методом useAuthSignout.

session

GET /api/auth/session

Возвращает безопасный для клиента объект сессии - или пустой объект, если сессия отсутствует. Содержимое возвращаемого объекта сессии настраивается с помощью функции обратного вызова сессии. Данные сессии также можно получить с помощью загрузчика маршрута useAuthSession.

csrf

GET /api/auth/csrf

Возвращает объект, содержащий CSRF-токен. В NextAuth.js CSRF-защита присутствует на всех маршрутах аутентификации. Он использует "метод двойной отправки кук", который использует HttpOnly-, host-only-куки. CSRF-токен, возвращаемый этой конечной точкой, должен передаваться как переменная формы с именем csrfToken во всех POST-запросах к любой конечной точке API.

providers

GET /api/auth/providers

Возвращает список настроенных сервисов OAuth и подробную информацию (например, URL-адреса входа и функции обратного вызова) для каждой службы. Это полезно для динамического создания пользовательских страниц регистрации и для проверки того, какие URL функций обратного вызова настроены для каждого настроенного провайдера OAuth.

Примеры

GitHub

  1. Следуйте руководству GitHub OAuth для получения GitHub Client ID, GitHub Client Secrets и сгенерируйте AUTH_SECRET с помощью openssl rand -base64 32 или Secret Generator.
  2. Поскольку по умолчанию plugin@auth.ts использует GitHub в качестве примера, нам не нужно ничего менять. Однако можно использовать не только GitHub, но и других провайдеров, или добавить дополнительные. Auth.js также поддерживает множество дополнительных опций, которые могут быть установлены в этом файле.
src/routes/plugin@auth.ts
import { serverAuth$ } from '@builder.io/qwik-auth';
import GitHub from '@auth/core/providers/github';
import type { Provider } from '@auth/core/providers';
 
export const { onRequest, useAuthSession, useAuthSignin, useAuthSignout } = serverAuth$(
  ({ env }) => ({
    secret: env.get("AUTH_SECRET"),
    trustHost: true,
    providers: [
      GitHub({
        clientId: env.get("GITHUB_ID"),
        clientSecret: env.get("GITHUB_SECRET"),
      }),
    ] as Provider[],
  })
);

ВАЖНО: Обязательно сохраните экспорт onRequest, так как он используется для обработки перенаправления процесса аутентификации oAuth. После того как пользователь завершил процедуру oAuth, GitHub (или любой другой провайдер) перенаправит его обратно в приложение по адресу /api/auth/callback/github (или /api/auth/callback/[otherProvider]). Функция промежуточного ПО onRequest будет обрабатывать запросы к этой конечной точке и завершать процесс аутентификации oAuth.

  1. Для хранения секретов создайте или отредактируйте файл .env.local в корне вашего проекта
.env.local
GITHUB_ID=
GITHUB_SECRET=
AUTH_SECRET=

ВАЖНО: Пожалуйста, прочитайте документацию Qwik о Переменных среды, чтобы убедиться, что вы безопасно их используете. Секретные токены многих провайдеров должны храниться в безопасности и не раскрываться клиенту/браузеру.

  1. Теперь приложение готово к реализации аутентификации с помощью Auth.js.
  2. Наслаждайтесь!

Учётные данные

Предупреждение: Эта функциональность не приветствуется в Auth.js.

https://next-auth.js.org/providers/credentials

  • Функциональность, предоставляемая для аутентификации на основе учётных данных, намеренно ограничена, чтобы предотвратить использование паролей из-за присущих им рисков безопасности и дополнительной сложности, связанной с поддержкой имён пользователей и паролей.
  1. Поскольку в стандартном plugin@auth.ts в качестве примера используется GitHub, нам необходимо заменить его на Credentials.
src/routes/plugin@auth.ts
import { serverAuth$ } from '@builder.io/qwik-auth';
import Credentials from "@auth/core/providers/credentials";
import type { Provider } from '@auth/core/providers';
 
export const { onRequest, useAuthSession, useAuthSignin, useAuthSignout } = serverAuth$(
  ({ env }) => ({
    secret: env.get("AUTH_SECRET"),
    trustHost: true,
    providers: [
      Credentials({
        async authorize(credentials, req) {
          // Добавьте сюда логику для поиска пользователя по предоставленным учётным данным
          const user = {
            id: 1,
            name: "Mike",
            email: "mike@example.com",
          };
 
          return user;
        },
      }),
    ] as Provider[],
  })
);
  1. Создайте или отредактируйте файл .env.local в корне проекта для хранения секретов
.env.local
AUTH_SECRET=

ВАЖНО: Пожалуйста, прочитайте документацию Qwik о Переменных окружения, чтобы убедиться, что вы используете их безопасно. Многие секреты провайдера должны храниться в безопасности и не должны раскрываться клиенту/браузеру.

  1. Теперь приложение готово к реализации аутентификации с помощью Auth.js.
  2. Наслаждайтесь!

Защита маршрута

Доступ к данным сессии можно получить через маршрут event.sharedMap. Таким образом, маршрут может быть защищён и перенаправлен с помощью чего-то подобного, расположенного в layout.tsx или на странице index.tsx:

export const onRequest: RequestHandler = (event) => {
  const session: Session | null = event.sharedMap.get('session');
  if (!session || new Date(session.expires) < new Date()) {
    throw event.redirect(302, `/api/auth/signin?callbackUrl=${event.url.pathname}`);
  }
};

Примечание: При размещении в layout.tsx убедитесь, что место назначения перенаправления не находится в том же layout.tsx, иначе может возникнуть вечный цикл перенаправления.

Участники

Спасибо всем участникам, которые помогли сделать эту документацию лучше!

  • the-r3aper7
  • ulic75
  • jakovljevic-mladen
  • ayoub9494
  • igorbabko
  • yaikohi
  • mrhoodz
  • VinuB-Dev
  • hugomonte
  • VarPDev