Аутентификация в REST-фреймворке Django с помощью Auth.js

В этом уроке рассматривается, как реализовать систему аутентификации на основе Django REST (с помощью Django REST Framework) и интегрировать ее с Auth.js (ранее известной как NextAuth.js) на фронтенде. В нем рассматривается настройка аутентификации на основе учетных данных, а также социальной аутентификации с помощью Google.

Как заставить Django REST Framework и Auth.js работать вместе?

Как использовать Django и Next.js вместе?

Вступление

Стандартная система аутентификации Django - отличное готовое решение для полнофункциональных Django-проектов. Тем не менее, при использовании Django REST Framework с клиент-серверной архитектурой использовать встроенную аутентификацию Django может быть довольно сложно. Кроме того, Django не поддерживает социальную аутентификацию.

К счастью, вы можете раскрыть систему аутентификации Django как API и добавить поддержку социальных аутентификаций с помощью пакетов django-allauth и dj-rest-auth, разработанных сообществом. Как только ваша система авторизации будет открыта, подключиться к ней из вашего фронтенда будет относительно просто.

Вам интересно, как просто использовать пакеты django-allauth и dj-rest-auth для реализации системы аутентификации на основе Django REST? Ознакомьтесь с Django REST Framework Authentication.

Хотя аутентификацию на основе учетных данных легко реализовать на фронтенде, проблемы возникают при добавлении социальной аутентификации. Каждый социальный провайдер отличается от другого, и добавление поддержки каждого из них по отдельности может занять очень много времени.

В связи с этим появилась Auth.js, стандартизированное решение для аутентификации с открытым исходным кодом для Next.js, SvelteKit и SolidStart. Библиотека поддерживает аутентификацию на основе учетных данных, аутентификацию без пароля, социальную аутентификацию и многое другое. Она также поддерживает более 60 социальных провайдеров.

В этом руководстве мы рассмотрим, как реализовать систему аутентификации на основе Django REST и подключиться к ней с фронтенда Next.js. К концу этого урока у вас будет полностью рабочая система аутентификации, поддерживающая социальную аутентификацию. Сначала мы поработаем над бэкендом, а затем перейдем к фронтенду.

Реализованное решение будет иметь следующий поток аутентификации:

Django and Auth.js Diagram

Цели

К концу этого урока вы сможете:

  1. Реализация системы аутентификации на основе Django REST с помощью Django REST Framework, django-allauth и dj-rest-auth.
  2. Включите и настройте аутентификацию по JSON Web Token с помощью djangorestframework-simplejwt.
  3. Настройте django-cors-headers, чтобы избежать проблем с CORS при подключении из фронтенда.
  4. Настройте Auth.js для поддержки аутентификации на основе учетных данных, а также социальной аутентификации.
  5. Подключите систему аутентификации Django на основе REST с Auth.js.
  6. Добавьте социальную аутентификацию с помощью Google.

Backend

В этом разделе мы создадим новый проект Django, запустим специальное приложение для аутентификации, установим и настроим все зависимости, а также протестируем систему аутентификации через cURL.

Можете пропустить настройку Django и продолжить работу над своим проектом.

Установка Django

Начните с создания нового каталога для вашего проекта и виртуальной среды:

$ mkdir django-rest-authjs && cd django-rest-authjs
$ python3.11 -m venv env
$ source env/bin/activate
(env)$

Не стесняйтесь заменить virtualenv и Pip на Poetry или Pipenv. Подробнее об этом читайте в Modern Python Environments.

Далее установите Django и загрузите новый проект:

(env)$ pip install Django==4.2.3
(env)$ django-admin startproject core .

Переместите базу данных и запустите сервер:

(env)$ python manage.py migrate
(env)$ python manage.py runserver

Последнее, откройте ваш любимый веб-браузер и перейдите по адресу http://localhost:8000. Вы должны увидеть целевую страницу Django по умолчанию.

Приложение для аутентификации

Чтобы сделать наш проект более организованным, мы создадим специальное приложение для аутентификации. Приложение будет включать в себя представления аутентификации и URL.

Создайте новое приложение:

(env)$ python manage.py startapp authentication

Добавьте его в INSTALLED_APPS в core/settings.py:

# core/settings.py

INSTALLED_APPS = [
    # ...
    "authentication.apps.AuthenticationConfig",
]

Создайте на уровне приложения файл urls.py в папке "authentication":

# authentication/urls.py

from django.urls import path
from . import views

urlpatterns = [
    # URLs will come here
]

Зарегистрируйте приложение уровня urls.py глобально:

# core/urls.py

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('api/auth/', include('authentication.urls')),
    path("admin/", admin.site.urls),
]

Вот и все для начальной настройки.

Аутентификация на основе учетных данных

В этом разделе мы настроим Django REST Framework, django-allauth и dj-rest-auth для включения аутентификации на основе учетных данных. Кроме того, мы настроим django-cors-headers, чтобы избежать проблем с CORS при подключении с фронтенда.

Django REST Framework

Начните с установки Django REST Framework:

(env)$ pip install djangorestframework==3.14.0

Далее перейдите в core/settings.py и добавьте его в INSTALLED_APPS вместе с authtoken:

# core/settings.py

INSTALLED_APPS = [
    # ...
    "rest_framework",
    "rest_framework.authtoken",
]

Приложение authtoken необходимо, поскольку мы будем использовать аутентификацию на основе токенов вместо стандартной SessionAuthentication в Django. Токен-аутентификация больше подходит для клиент-серверных систем.

Существует две системы аутентификации на основе токенов, из которых мы можем выбрать:

Я рекомендую использовать JWT-аутентификацию с помощью плагина djangorestframework-simplejwt. Подход JWT лучше, потому что он более безопасен, проще для базы данных и имеет стандартизированные HTTP-ответы.

Чтобы установить плагин Simple JWT, выполните следующие действия:

$(env) pip install djangorestframework-simplejwt==5.2.2

Далее добавьте его в INSTALLED_APPS под rest_framework.authtoken:

# core/settings.py

INSTALLED_APPS = [
    # ...
    "rest_framework.authtoken",
    "rest_framework_simplejwt",
]

Затем добавьте следующие настройки в нижнюю часть файла core/settings.py:

# core/settings.py

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=7),
    "ROTATE_REFRESH_TOKENS": False,
    "BLACKLIST_AFTER_ROTATION": False,
    "UPDATE_LAST_LOGIN": True,
    "SIGNING_KEY": "complexsigningkey",  # generate a key and replace me
    "ALGORITHM": "HS512",
}

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

Не забывайте об импорте:

from datetime import timedelta

И наконец, измените класс аутентификации DRF по умолчанию:

# core/settings.py

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ]
}

django-allauth

Сначала установите django-allauth:

(env)$ pip install django-allauth==0.52.0

Далее перейдите к файлу settings.py и добавьте его в INSTALLED_APPS вместе с приложениями account и socialaccount:

# core/settings.py

INSTALLED_APPS = [
    "django.contrib.sites",  # make sure 'django.contrib.sites' is installed
    # ...
    "allauth",
    "allauth.account",
    "allauth.socialaccount",  # add if you want social authentication
]

django-allauth зависит от "сайтов" фреймворка Django, поэтому убедитесь, что он у вас установлен. Кроме того, убедитесь, что у вас установлены SITE_ID:

# core/settings.py

SITE_ID = 1  # make sure SITE_ID is set

Далее следует отключить функцию проверки электронной почты:

# core/settings.py

ACCOUNT_EMAIL_REQUIRED = False
ACCOUNT_EMAIL_VERIFICATION = "none"

На этом шаге проверка электронной почты должна быть отключена, поскольку наш проект Django не настроен на отправку писем. Кроме того, у нас нет конечных точек, которые позволили бы пользователям проверять свои адреса электронной почты. Подробнее об этом в конце руководства.

И наконец, еще раз мигрируйте базу данных:

(env)$ python manage.py migrate

dj-rest-auth

Начните с установки пакета dj-rest-auth:

(env)$ pip install "dj-rest-auth[with_social]==4.0.0"

Нам нужно использовать спецификатор with_social, поскольку мы хотим включить стандартный процесс регистрации. Кроме того, мы будем использовать этот пакет позже, когда включим социальную аутентификацию.

Далее перейдите к файлу core/settings.py и добавьте его в INSTALLED_APPS:

# core/settings.py

INSTALLED_APPS = [
    # ...
    "dj_rest_auth",
    "dj_rest_auth.registration",
]

Затем добавьте следующие параметры конфигурации в settings.py:

# core/settings.py

REST_AUTH = {
    "USE_JWT": True,
    "JWT_AUTH_HTTPONLY": False,
}

Примечания:

  1. USE_JWT требуется, потому что мы используем djangorestframework-simplejwt.
  2. JWT_AUTH_HTTPONLY должен быть выключен, иначе dj-rest-auth не будет посылать токены обновления.

Для получения информации обо всех настройках REST_AUTH ознакомьтесь с официальной документацией .

На момент написания статьи в официальном руководстве по установке говорится о необходимости регистрации dj_rest_auth.urls. Я не рекомендую этого делать, поскольку URL по умолчанию содержат неработающие URL, если они неправильно настроены (например, сброс пароля, проверка электронной почты).

Вместо этого посмотрите исходный код dj-rest-auth и вручную выберите нужные вам URL-адреса:

Обновите authentication/urls.py следующим образом:

# authentication/urls.py

from dj_rest_auth.jwt_auth import get_refresh_view
from dj_rest_auth.registration.views import RegisterView
from dj_rest_auth.views import LoginView, LogoutView, UserDetailsView
from django.urls import path
from rest_framework_simplejwt.views import TokenVerifyView

urlpatterns = [
    path("register/", RegisterView.as_view(), name="rest_register"),
    path("login/", LoginView.as_view(), name="rest_login"),
    path("logout/", LogoutView.as_view(), name="rest_logout"),
    path("user/", UserDetailsView.as_view(), name="rest_user_details"),
    path("token/verify/", TokenVerifyView.as_view(), name="token_verify"),
    path("token/refresh/", get_refresh_view().as_view(), name="token_refresh"),
]
django-cors-headers

Последним пакетом, который нам нужно установить и настроить, является django-cors-headers. Этот пакет будет устанавливать правильные CORS-заголовки при взаимодействии нашего фронтенда с бэкендом.

Сначала установите его, выполнив следующую команду:

$(env) pip install django-cors-headers==4.1.0

Добавьте его в INSTALLED_APPS в core/settings.py:

# core/settings.py

INSTALLED_APPS = [
    # ...
    "corsheaders",
]

Добавьте CorsMiddleware в список MIDDLEWARE:

# core/settings.py

MIDDLEWARE = [
    # ...
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    # ...
]

Вы должны разместить CorsMiddleware как можно выше. Особенно перед любым промежуточным ПО, которое может генерировать ответы, таким как CommonMiddleware от Django или WhiteNoiseMiddleware от Whitenoise.

Наконец, добавьте следующие настройки в нижнюю часть файла core/settings.py:

# core/settings.py

if DEBUG:
    CORS_ALLOW_ALL_ORIGINS = True
else:
    CORS_ALLOWED_ORIGINS = [
        "http://localhost:3000/",
        "http://127.0.0.1:3000/",
    ]

Отлично! Вот и все.

Теперь у вас есть полностью рабочая система аутентификации на основе учетных данных, использующая веб-токены JSON.

Тестирование

Для тестирования API мы будем использовать cURL. Мы также передадим ответы в jq, чтобы автоматически отформатировать и выделить их цветом.

Начните с запуска сервера разработки Django:

(env)$ python manage.py runserver
Регистрация

Сначала создайте пользователя:

$ curl -XPOST -H "Content-type: application/json" -d '{
      "username": "user1",
      "password1": "complexpassword123",
      "password2": "complexpassword123"
  }' 'http://localhost:8000/api/auth/register/' | jq

Вы должны получить аналогичный ответ:

{
  "access": "eyJhb...",
  "refresh": "eyJhb...",
  "user": {
    "pk": 1,
    "username": "user1",
    "email": ""
  }
}

Теперь вы можете использовать токен access для аутентификации. Токен access действителен в течение времени, указанного в core/settings.py, а затем его необходимо обновить с помощью токена refresh.

Вход

Войдите в систему как только что созданный пользователь:

$ curl -XPOST -H "Content-type: application/json" -d '{
      "username": "user1",
      "password": "complexpassword123"
  }' 'http://localhost:8000/api/auth/login/' | jq

Ответ будет похож на этот:

{
  "access": "eyJhb...",
  "refresh": "eyJhb...",
  "user": {
    "pk": 1,
    "username": "user1",
    "email": ""
  }
}
Пользовательские данные

Получение данных о пользователе с помощью полученного access токена:

$ curl -XGET -H 'Authorization: Bearer <your_access_token>' \
    -H "Content-type: application/json" 'http://localhost:8000/api/auth/user/' | jq

Ответ:

{
  "pk": 1,
  "username": "user1",
  "email": ""
}
Проверка токена

Чтобы проверить, что токен все еще действителен, вы можете передать его в /api/auth/token/verify/ следующим образом:

$ curl -XPOST -H "Content-type: application/json" -d '{
    "token": "<your_access_token>"
  }' 'http://localhost:8000/api/auth/token/verify/' | jq

Ответ:

{}

Коды состояния:

  • 200 означает, что токен все еще действителен.
  • 4xx означает, что токен больше не действителен.
Обновление токена

Чтобы получить обновленный токен access, вам придется использовать свой токен refresh следующим образом:

$ curl -XPOST -H "Content-type: application/json" -d '{
    "refresh": "your_refresh_token>"
  }' 'http://localhost:8000/api/auth/token/refresh/' | jq

Ответ:

{
  "access": "eyJhb...",
  "access_expiration": "2023-07-11T17:42:22.480739Z"
}

В ответе будет содержаться новый токен access и время истечения срока действия.

Выход

Чтобы выйти из системы, попробуйте отправить следующий запрос:

$ curl -XPOST -H 'Authorization: Bearer <your_access-token>' \
    -H "Content-type: application/json" 'http://localhost:8000/api/auth/logout/' | jq

Ответ:

{
  "detail": "Neither cookies or blacklist are enabled. Delete the token on client."
}

Поскольку мы отключили cookies и не настроили черный список, эта конечная точка ничего не делает. Нет необходимости вызывать ее при выходе пользователя. Единственное, что вам нужно сделать, это удалить токен с клиента.

Если вам нужен более безопасный подход, создайте черный список с помощью приложения JWT Blacklist.

Социальная аутентификация с помощью Google

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

Если вы не заинтересованы в настройке социальной аутентификации, смело пропускайте этот раздел.

Настройка

Чтобы включить функцию Google sign up, сначала нужно создать ключ клиента OAuth. Перейдите в свою Google Cloud Console, выберите проект, который вы хотите использовать, и найдите "API Credentials":

Google Console API Credentials Search

Далее нажмите на кнопку "Создать учетные данные" и выберите "OAuth Client ID" в выпадающем списке:

Google Console OAuth Client ID Create

Выберите "Веб-приложение", выберите пользовательское имя и добавьте URL-адрес вашего фронтенда в качестве URI авторизованного перенаправления. Для упрощения тестирования я рекомендую также добавить:

И наконец, нажмите кнопку "Создать", чтобы сгенерировать учетные данные:

Google Console OAuth Form

Вам будут представлены "Идентификатор клиента" и "Секрет клиента". Запишите их, поскольку они понадобятся нам на следующем этапе.

Двигаясь дальше, вернитесь в свой проект Django и добавьте следующее приложение в INSTALLED_APPS:

# core/settings.py

INSTALLED_APPS = [
    # ...
    "allauth.socialaccount.providers.google",
]

Далее добавьте SOCIALACCOUNT_PROVIDERS настройку в core/settings.py следующим образом:

# core/settings.py

SOCIALACCOUNT_PROVIDERS = {
    "google": {
        "APP": {
            "client_id": "<your google client id>",  # replace me
            "secret": "<your google secret>",        # replace me
            "key": "",                               # leave empty
        },
        "SCOPE": [
            "profile",
            "email",
        ],
        "AUTH_PARAMS": {
            "access_type": "online",
        },
        "VERIFIED_EMAIL": True,
    },
}

Обязательно замените <your google client id> и <your google secret> на ваши настоящие ключи. Оставьте свойство key пустым, поскольку оно не требуется для социальной аутентификации Google.

Добавьте новый вид в authentication/views.py, который наследуется от SocialLoginView:

# authentication/views.py

from dj_rest_auth.registration.views import SocialLoginView
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client


class GoogleLogin(SocialLoginView):
    adapter_class = GoogleOAuth2Adapter
    callback_url = "http://127.0.0.1:3000/"
    client_class = OAuth2Client

Последнее, добавьте следующие два URL в authentication/urls.py:

# authentication/urls.py

from allauth.socialaccount.views import signup
from authentication.views import GoogleLogin

urlpatterns = [
    # ...
    path("google/", GoogleLogin.as_view(), name="google_login"),
]

Не забывайте об импорте:

from authentication.views import GoogleLogin

Тестирование

Чтобы проверить, работает ли конечная точка API, сначала нужно получить id_token, а затем отправить его на конечную точку бэкенда. Проще всего получить id_token через Google's OAuth 2.0 Playground.

Чтобы получить токен, вы можете следовать этой замечательной статье.

Далее передайте id_token в качестве access_token в теле запроса cURL:

$ curl -XPOST -H "Content-type: application/json" -d '{
    "access_token": "<your_id_token>"
  }' 'http://localhost:8000/api/auth/google/'

Вы должны получить аналогичный ответ:

{
  "access": "eyJhb...",
  "refresh": "eyJhb...",
  "user": {
    "pk": 2,
    "username": "user2",
    "email": ""
  }
}

Теперь вы можете использовать токен access для аутентификации.

Frontend

В этом разделе мы загрузим проект Next.js, настроим Chakra UI, установим и настроим Auth.js, включим аутентификацию на основе учетных данных и социальную аутентификацию. Наконец, мы протестируем систему аутентификации.

У веб-приложения будут следующие конечные точки:

  1. / предлагает пользователю войти в систему.
  2. /profile отображает информацию об учетной записи пользователя.
  3. /api/auth/... обрабатывается динамическим API-маршрутизатором Auth.js.

Можете пропустить первые два раздела и продолжить работу над собственным проектом.

Установка Next.js

Начните с использования create-next-app для создания проекта Next.js:

$ npx create-next-app@13.4.9 django-rest-authjs-frontend

√ Would you like to use TypeScript? ... Yes
√ Would you like to use ESLint? ... Yes
√ Would you like to use Tailwind CSS? ... No
√ Would you like to use `src/` directory? ... No
√ Would you like to use App Router? (recommended) ... No
√ Would you like to customize the default import alias? ... No

Creating a new Next.js app in ~\django-rest-authjs-frontend.

Для простоты я рекомендую вам ответить "Да" только на TypeScript и ESLint. Вы также можете подписаться на TailwindCSS, если он вам больше нравится, чем Chakra UI.

Настройка пользовательского интерфейса чакры

Чтобы упростить процесс создания пользовательского интерфейса, мы будем использовать Chakra UI - минималистичную, модульную и доступную библиотеку компонентов для быстрого создания пользовательских интерфейсов React. В библиотеке есть встроенные компоненты, специализированные хуки, система стилей и многое другое.

Не стесняйтесь поменять Chakra UI на MUI, AntDesign, TailwindCSS или любую другую библиотеку фронтенда.

Установите его, выполнив следующую команду:

$ npm i @chakra-ui/react@2.5.5 @chakra-ui/next-js@2.1.4 @emotion/react@11.10.6 \
        @emotion/styled@11.10.6 framer-motion@10.12.4

Далее создайте новую тему с помощью extendTheme() и оберните Component с помощью ChakraProvider в pages/_app.tsx следующим образом:

// pages/_app.tsx

import type {AppProps} from "next/app";
import {ChakraProvider, extendTheme} from "@chakra-ui/react";

export const theme = extendTheme({});

export default function App({Component, pageProps: {session, ...pageProps}}: AppProps) {
  return (
    <ChakraProvider theme={theme}>
      <Component {...pageProps} />
    </ChakraProvider>
  );
}

Не забывайте об импорте:

import {ChakraProvider, extendTheme} from "@chakra-ui/react";

Мы создали новую тему, так как в следующем шаге нам нужно будет передать ее в скрипт цветового режима. Кроме того, определив тему таким образом, вы сможете легко настроить Chakra в будущем.

Далее займемся сценарием цветового режима.

Сценарий цветового режима обеспечивает корректную работу синхронизации локального хранилища. Он должен быть добавлен перед любым содержимым (в нашем случае в файл pages/_document.tsx).

Включите ColorModeScript в ваш pages/_document.tsx следующим образом:

// pages/_document.tsx

import {Head, Html, Main, NextScript} from "next/document";
import {ColorModeScript} from "@chakra-ui/react";
import {theme} from "./_app";

export default function Document() {
  return (
    <Html lang="en">
      <Head/>
      <body>
        <ColorModeScript initialColorMode={theme.config.initialColorMode}/>
        <Main/>
        <NextScript/>
      </body>
    </Html>
  );
}

Опять же, не забывайте об импорте:

import {ColorModeScript} from "@chakra-ui/react";
import {theme} from "./_app";

Виды

Давайте заменим стандартную индексную страницу Next.js на приглашение к входу в систему.

Перейдите по адресу pages/index.tsx и замените его содержимое на следующее:

// pages/index.tsx

import {Box, Button, Spinner, Text, VStack} from "@chakra-ui/react";

export default function Home() {
  return (
    <Box m={8}>
      <VStack>
        <Text>You are not authenticated.</Text>
        <Button colorScheme="blue" onClick={() => console.log("Beep boop")}>
          Sign in
        </Button>
      </VStack>
    </Box>
  );
}

Запустите сервер разработки:

$ npx next dev

Откройте свой любимый веб-браузер и перейдите по адресу http://127.0.0.1:3000/. Вы должны увидеть страницу с сообщением "Вы не прошли аутентификацию" и кнопкой входа. Щелчок по кнопке должен вывести отладочное сообщение на консоль.

Next.js Chakra UI Index

Превосходно. Вот и все для первоначальной настройки.

Аутентификация на основе учетных данных

В этом разделе мы установим Auth.js и займемся аутентификацией на основе учетных данных.

Axios

Прежде чем работать с Auth.js, установим Axios - простой HTTP-клиент на основе обещаний для браузера и Node.js. Эта библиотека позволит нам отправлять HTTP-запросы к нашему бэкенду с элегантным и лаконичным синтаксисом.

Установите его через NPM:

$ npm install axios@1.3.6

Auth.js

Двигаясь дальше, установите Auth.js:

$ npm install next-auth@4.22.1

Чтобы Auth.js работал правильно, нам сначала нужно установить некоторые переменные окружения. В Next.js переменные окружения автоматически загружаются из .env.local из корня проекта.

Создайте файл .env.local со следующим содержимым:

# .env.local

NEXTAUTH_URL=http://127.0.0.1:3000/
NEXTAUTH_SECRET=verycomplexsecretkey
NEXTAUTH_BACKEND_URL=http://127.0.0.1:8000/api/
NEXT_PUBLIC_BACKEND_URL=http://127.0.0.1:8000/api/

Далее нам нужно создать обработчик динамических маршрутов и папку конфигураций Auth.js. Создайте папку "auth" в папке "pages/api" и файл [...nextauth].js во вновь созданной папке.

pages/
├── api/
│   └── auth/
│       └── [...nextauth].js
├── _app.tsx
├── _document.tsx
└── index.tsx

Прежде чем приступить к работе с кодом, давайте проясним, как работают сессии Auth.js. Auth.js поставляется с двумя встроенными способами работы с сессиями:

  1. Использование базы данных (сессия хранится в базе данных)
  2. Использование JWT-токена (сессия хранится в токене)

Поскольку мы используем Django в качестве бэкенда, нам придется использовать второй подход. Недостатком этого подхода является утомительная передача аргументов и хранение accessToken, refreshToken и других чувствительных переменных в JWT-токене.

Вставьте следующее содержимое в [...nextauth].js:

// pages/api/auth/[...nextauth].js

import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import axios from "axios";

// These two values should be a bit less than actual token lifetimes
const BACKEND_ACCESS_TOKEN_LIFETIME = 45 * 60;            // 45 minutes
const BACKEND_REFRESH_TOKEN_LIFETIME = 6 * 24 * 60 * 60;  // 6 days

const getCurrentEpochTime = () => {
  return Math.floor(new Date().getTime() / 1000);
};

const SIGN_IN_HANDLERS = {
  "credentials": async (user, account, profile, email, credentials) => {
    return true;
  },
};
const SIGN_IN_PROVIDERS = Object.keys(SIGN_IN_HANDLERS);

export const authOptions = {
  secret: process.env.AUTH_SECRET,
  session: {
    strategy: "jwt",
    maxAge: BACKEND_REFRESH_TOKEN_LIFETIME,
  },
  providers: [
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        username: {label: "Username", type: "text"},
        password: {label: "Password", type: "password"}
      },
      // The data returned from this function is passed forward as the
      // `user` variable to the signIn() and jwt() callback
      async authorize(credentials, req) {
        try {
          const response = await axios({
            url: process.env.NEXTAUTH_BACKEND_URL + "auth/login/",
            method: "post",
            data: credentials,
          });
          const data = response.data;
          if (data) return data;
        } catch (error) {
          console.error(error);
        }
        return null;
      },
    }),
  ],
  callbacks: {
    async signIn({user, account, profile, email, credentials}) {
      if (!SIGN_IN_PROVIDERS.includes(account.provider)) return false;
      return SIGN_IN_HANDLERS[account.provider](
        user, account, profile, email, credentials
      );
    },
    async jwt({user, token, account}) {
      // If `user` and `account` are set that means it is a login event
      if (user && account) {
        let backendResponse = account.provider === "credentials" ? user : account.meta;
        token["user"] = backendResponse.user;
        token["access_token"] = backendResponse.access;
        token["refresh_token"] = backendResponse.refresh;
        token["ref"] = getCurrentEpochTime() + BACKEND_ACCESS_TOKEN_LIFETIME;
        return token;
      }
      // Refresh the backend token if necessary
      if (getCurrentEpochTime() > token["ref"]) {
        const response = await axios({
          method: "post",
          url: process.env.NEXTAUTH_BACKEND_URL + "auth/token/refresh/",
          data: {
            refresh: token["refresh_token"],
          },
        });
        token["access_token"] = response.data.access;
        token["refresh_token"] = response.data.refresh;
        token["ref"] = getCurrentEpochTime() + BACKEND_ACCESS_TOKEN_LIFETIME;
      }
      return token;
    },
    // Since we're using Django as the backend we have to pass the JWT
    // token to the client instead of the `session`.
    async session({token}) {
      return token;
    },
  }
};

export default NextAuth(authOptions);

Примечания:

  1. Сначала мы определили время жизни токенов бэкенда и метод для получения UNIX-времени.
  2. Обработчики входа в систему будут использоваться только при социальной аутентификации. При использовании аутентификации на основе учетных данных проверка уже выполняется в методе authorize().
  3. Затем мы определили authOptions и добавили CredentialsProvider к его providers.
  4. В CredentialsProvider мы определили, какие поля должна содержать форма входа, и определили метод authorize(), который передает учетные данные на бэкэнд для их проверки.
  5. Затем мы использовали обратные вызовы signIn(), jwt() и session() для присоединения данных к JWT-токену и передачи их клиенту. Обновление токена на задней стороне было реализовано в обратном вызове jwt().

Чтобы узнать больше об обратных вызовах Auth.js, посмотрите Auth.js Callbacks.

И последнее, оберните ваше приложение с помощью SessionProvider в pages/_app.tsx следующим образом:

// pages/_app.tsx

import type {AppProps} from "next/app";
import {SessionProvider} from "next-auth/react";
import {ChakraProvider, extendTheme} from "@chakra-ui/react";

export const theme = extendTheme({});

export default function App({Component, pageProps: {session, ...pageProps}}: AppProps) {
  return (
    <SessionProvider session={session}>
      <ChakraProvider theme={theme}>
        <Component {...pageProps} />
      </ChakraProvider>
    </SessionProvider>
  );
}

Не забывайте об импорте:

import {SessionProvider} from "next-auth/react";

При помощи SessionProvider вы сможете получить доступ к сессии через крючок useSession().

Чтобы внедрить тип Session, создайте в корне проекта новую папку с именем "types". Затем добавьте в нее следующий файл с именем next-auth.d.ts.

Виды

Продолжаем, давайте поработаем над видами.

Сначала измените ваш pages/index.tsx следующим образом:

// pages/index.tsx

import {useRouter} from "next/router";
import {signIn, useSession} from "next-auth/react";
import {Box, Button, Spinner, Text, VStack} from "@chakra-ui/react";

export default function Home() {

  const router = useRouter();
  const {data: session, status} = useSession();

  if (status == "loading") {
    return <Spinner size="lg"/>;
  }

  // If the user is authenticated redirect to `/profile`
  if (session) {
    router.push("profile");
    return;
  }

  return (
    <Box m={8}>
      <VStack>
        <Text>You are not authenticated.</Text>
        <Button
            colorScheme="blue"
            onClick={() => signIn(undefined, {callbackUrl: "/profile"})}
        >
          Sign in
        </Button>
      </VStack>
    </Box>
  );
}

Примечания:

  1. Мы использовали хук useSession() для получения сессии из SessionProvider.
  2. Пока сессия загружается, пользователю представляется компонент Spinner.
  3. В случае если пользователь уже вошел в систему, мы перенаправляем его на /profile через useRouter().
  4. Кнопка входа теперь вызывает метод signIn().

Далее создайте новый файл с именем profile.tsx в папке "pages" со следующим содержимым:

// pages/profile.tsx

import {useState} from "react";
import {signOut, useSession} from "next-auth/react";
import {Box, Button, Code, HStack, Spinner, Text, VStack} from "@chakra-ui/react";
import axios from "axios";

export default function Home() {

  const {data: session, status} = useSession({required: true});
  const [response, setResponse] = useState("{}");

  const getUserDetails = async (useToken: boolean) => {
    try {
      const response = await axios({
        method: "get",
        url: process.env.NEXT_PUBLIC_BACKEND_URL + "auth/user/",
        headers: useToken ? {Authorization: "Bearer " + session?.access_token} : {},
      });
      setResponse(JSON.stringify(response.data));
    } catch (error) {
      setResponse(error.message);
    }
  };

  if (status == "loading") {
    return <Spinner size="lg"/>;
  }

  if (session) {
    return (
      <Box m={8}>
        <VStack>
          <Text>PK: {session.user.pk}</Text>
          <Text>Username: {session.user.username}</Text>
          <Text>Email: {session.user.email || "Not provided"}</Text>
          <Code>
            {response}
          </Code>
        </VStack>
        <HStack justifyContent="center" mt={4}>
          <Button colorScheme="blue" onClick={() => getUserDetails(true)}>
            User details (with token)
          </Button>
          <Button colorScheme="orange" onClick={() => getUserDetails(false)}>
            User details (without token)
          </Button>
          <Button colorScheme="red" onClick={() => signOut({callbackUrl: "/"})}>
            Sign out
          </Button>
        </HStack>
      </Box>
    );
  }

  return <></>;
}

Примечания:

  1. Мы использовали useRouter() и useSession() так же, как и в предыдущем фрагменте кода.
  2. Мы добавили метод getUserDetails(), который получает информацию о пользователе и отображает ее.
  3. Нажатие на кнопку выхода вызывает метод signOut().

Тестирование

Перезапустите сервер разработки Next:

$ npx next dev

Затем откройте свой любимый веб-браузер и перейдите по адресу http://127.0.0.1:3000. Нажмите на кнопку "Войти" и убедитесь, что вас перенаправили на форму входа. Заполните форму и нажмите кнопку "Войти с учетными данными".

Next.js Auth.js Credentials Authentication

При успешном входе вы будете перенаправлены на страницу профиля. Попробуйте получить информацию о пользователе с помощью accessToken.

Next.js Auth.js User Profile

Социальная аутентификация с помощью Google

В этом разделе мы настроим социальную аутентификацию с помощью Google. Интеграция других социальных провайдеров с Auth.js практически аналогична. Просто убедитесь, что ваш бэкенд Django работает, прежде чем переходить к фронтенду.

Не стесняйтесь пропустить этот раздел, если вас не интересует социальная аутентификация.

Настройка

Сначала добавьте следующие две переменные окружения в .env.local:

# .env.local

# ...
GOOGLE_CLIENT_ID=<your_google_client_id>
GOOGLE_CLIENT_SECRET=<your_google_secret>

Обязательно замените <your_google_client_id> и <your_google_secret> на ваши реальные ключи. Помните, что ключи должны совпадать с ключами, указанными в core/settings.py.

Далее зарегистрируйте обработчик входа в систему Google в файле [...nextauth].js:

// pages/api/auth/[...nextauth].js

const SIGN_IN_HANDLERS = {
  // ...
  "google": async (user, account, profile, email, credentials) => {
    try {
      const response = await axios({
        method: "post",
        url: process.env.NEXTAUTH_BACKEND_URL + "auth/google/",
        data: {
          access_token: account["id_token"]
        },
      });
      account["meta"] = response.data;
      return true;
    } catch (error) {
      console.error(error);
      return false;
    }
  }
};

Единственная цель этого обработчика - перенаправить id_token пользователя на конечную точку Django social. Django позаботится обо всем остальном - например, создаст аккаунт, зарегистрирует пользователя и т. д.

Затем добавьте GoogleProvider в нижнюю часть массива providers в файле [...nextauth].js:

// pages/api/auth/[...nextauth].js

export const authOptions = {
  // ...
  providers: [
    // ...
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      authorization: {
        params: {
          prompt: "consent",
          access_type: "offline",
          response_type: "code"
        }
      }
    }),
  ],
  // ...
};

Не забудьте импортировать GoogleProvider в верхней части файла:

import GoogleProvider from "next-auth/providers/google";

Это все, что нам нужно сделать.

Тестирование

Перед тестированием убедитесь, что ваш бэкенд Django запущен и доступен по URL, указанному в .env.local. После этого завершите процесс сервера разработки Next и запустите его снова:

$ npx next dev

Перейдите по адресу http://127.0.0.1:3000/ в вашем любимом веб-браузере и нажмите на кнопку "Войти". Выберите "Войти с помощью Google" и проверьте, работает ли социальная аутентификация.

Next.js Auth.js Google Authentication

Заключение

В этом руководстве мы успешно реализовали систему аутентификации Django REST и связали ее с Auth.js. Реализованная система поддерживает аутентификацию на основе учетных данных, а также социальную аутентификацию с помощью Google. Она построена таким образом, что к ней легко добавить других социальных провайдеров, таких как Twitter, GitHub и т. д.

Исходный код доступен на репо django-rest-authjs GitHub.

Будущие шаги

  1. Убедитесь, что реализована ротация токенов обновления для социальных провайдеров.
  2. По умолчанию Auth.js не заботится о создании учетных записей. Если вы хотите включить регистрацию пользователей, то либо сделайте это в методе authorize(), либо создайте специальную страницу регистрации.
  3. Чтобы включить функцию проверки электронной почты и сброса пароля, ознакомьтесь с разделом "Проверка электронной почты и сброс пароля" в статье Django REST Framework Authentication.
  4. Чтобы включить функцию привязки социальных аккаунтов, обратитесь к статье "Social Connect Views".
Вернуться на верх