Аутентификация в 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 REST с помощью Django REST Framework, django-allauth и dj-rest-auth.
- Включите и настройте аутентификацию по JSON Web Token с помощью djangorestframework-simplejwt.
- Настройте django-cors-headers, чтобы избежать проблем с CORS при подключении из фронтенда.
- Настройте Auth.js для поддержки аутентификации на основе учетных данных, а также социальной аутентификации.
- Подключите систему аутентификации Django на основе REST с Auth.js.
- Добавьте социальную аутентификацию с помощью 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. Токен-аутентификация больше подходит для клиент-серверных систем.
Существует две системы аутентификации на основе токенов, из которых мы можем выбрать:
- Встроенная TokenAuthentication
- JWT-аутентификация через сторонний пакет
Я рекомендую использовать 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,
}
Примечания:
USE_JWT
требуется, потому что мы используем djangorestframework-simplejwt.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":
Далее нажмите на кнопку "Создать учетные данные" и выберите "OAuth Client ID" в выпадающем списке:
Выберите "Веб-приложение", выберите пользовательское имя и добавьте URL-адрес вашего фронтенда в качестве URI авторизованного перенаправления. Для упрощения тестирования я рекомендую также добавить:
- http://127.0.0.1:3000
- http://127.0.0.1:3000/api/auth/callback/google
- https://developers.google.com/oauthplayground
И наконец, нажмите кнопку "Создать", чтобы сгенерировать учетные данные:
Вам будут представлены "Идентификатор клиента" и "Секрет клиента". Запишите их, поскольку они понадобятся нам на следующем этапе.
Двигаясь дальше, вернитесь в свой проект 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, включим аутентификацию на основе учетных данных и социальную аутентификацию. Наконец, мы протестируем систему аутентификации.
У веб-приложения будут следующие конечные точки:
/
предлагает пользователю войти в систему./profile
отображает информацию об учетной записи пользователя./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/. Вы должны увидеть страницу с сообщением "Вы не прошли аутентификацию" и кнопкой входа. Щелчок по кнопке должен вывести отладочное сообщение на консоль.
Превосходно. Вот и все для первоначальной настройки.
Аутентификация на основе учетных данных
В этом разделе мы установим 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 поставляется с двумя встроенными способами работы с сессиями:
- Использование базы данных (сессия хранится в базе данных)
- Использование 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);
Примечания:
- Сначала мы определили время жизни токенов бэкенда и метод для получения UNIX-времени.
- Обработчики входа в систему будут использоваться только при социальной аутентификации. При использовании аутентификации на основе учетных данных проверка уже выполняется в методе
authorize()
. - Затем мы определили
authOptions
и добавилиCredentialsProvider
к егоproviders
. - В
CredentialsProvider
мы определили, какие поля должна содержать форма входа, и определили методauthorize()
, который передает учетные данные на бэкэнд для их проверки. - Затем мы использовали обратные вызовы
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>
);
}
Примечания:
- Мы использовали хук
useSession()
для получения сессии изSessionProvider
. - Пока сессия загружается, пользователю представляется компонент
Spinner
. - В случае если пользователь уже вошел в систему, мы перенаправляем его на
/profile
черезuseRouter()
. - Кнопка входа теперь вызывает метод
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 <></>;
}
Примечания:
- Мы использовали
useRouter()
иuseSession()
так же, как и в предыдущем фрагменте кода. - Мы добавили метод
getUserDetails()
, который получает информацию о пользователе и отображает ее. - Нажатие на кнопку выхода вызывает метод
signOut()
.
Тестирование
Перезапустите сервер разработки Next:
$ npx next dev
Затем откройте свой любимый веб-браузер и перейдите по адресу http://127.0.0.1:3000. Нажмите на кнопку "Войти" и убедитесь, что вас перенаправили на форму входа. Заполните форму и нажмите кнопку "Войти с учетными данными".
При успешном входе вы будете перенаправлены на страницу профиля. Попробуйте получить информацию о пользователе с помощью accessToken
.
Социальная аутентификация с помощью 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" и проверьте, работает ли социальная аутентификация.
Заключение
В этом руководстве мы успешно реализовали систему аутентификации Django REST и связали ее с Auth.js. Реализованная система поддерживает аутентификацию на основе учетных данных, а также социальную аутентификацию с помощью Google. Она построена таким образом, что к ней легко добавить других социальных провайдеров, таких как Twitter, GitHub и т. д.
Исходный код доступен на репо django-rest-authjs GitHub.
Будущие шаги
- Убедитесь, что реализована ротация токенов обновления для социальных провайдеров.
- По умолчанию Auth.js не заботится о создании учетных записей. Если вы хотите включить регистрацию пользователей, то либо сделайте это в методе
authorize()
, либо создайте специальную страницу регистрации. - Чтобы включить функцию проверки электронной почты и сброса пароля, ознакомьтесь с разделом "Проверка электронной почты и сброс пароля" в статье Django REST Framework Authentication.
- Чтобы включить функцию привязки социальных аккаунтов, обратитесь к статье "Social Connect Views".