Аутентификация в Django REST-фреймворке

В этом уроке рассматривается, как реализовать систему аутентификации на основе REST в Django с помощью пакетов django-allauth и dj-rest-auth. Кроме того, в статье показано, как настроить социальную аутентификацию с помощью Google при использовании Django REST Framework.

Как добавить конечные точки аутентификации и регистрации в Django Rest Framework?

Вступление

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

К счастью, пакеты django-allauth и dj-rest-auth решают эти две проблемы:

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

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

В результате я решил написать это руководство, цель которого - объяснить один из возможных подходов к настройке этих двух пакетов для совместной работы. Руководство состоит из трех основных частей:

  1. Аутентификация на основе учетных данных
  2. Проверка электронной почты и сброс пароля
  3. Социальная аутентификация

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

Настройка проекта

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

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

Django Setup

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

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

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

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

(env)$ pip install Django==4.2.1
(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

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

# 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 с authentication приложением:

# 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 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 необходимо, поскольку мы будем использовать TokenAuthentication вместо стандартной SessionAuthentication в Django. Токен-аутентификация - это простая схема HTTP-аутентификации на основе токенов, которая подходит для клиент-серверных установок.

Далее добавьте следующее в нижнюю часть файла настроек, чтобы определить классы аутентификации по умолчанию:

# settings/core.py

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.TokenAuthentication",
    ]
}

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

(env)$ python manage.py migrate

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

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

(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",
]

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

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

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

# authentication/urls.py

from dj_rest_auth.registration.views import RegisterView
from dj_rest_auth.views import LoginView, LogoutView, UserDetailsView
from django.urls import path


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"),
]

Вот и все. Теперь базовый поток аутентификации должен работать. Давайте протестируем его.

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

Для тестирования 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

По умолчанию вы получите пустой ответ.

Вход

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

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

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

{
    "key": "<your_token>"
}

Пользовательские данные

Теперь передайте токен в заголовке Authorization, чтобы получить данные о пользователе:

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

Ответ:

{
    "pk": 1,
    "username": "user1",
    "email": "user1@example.com",
    "first_name": "",
    "last_name": ""
}

Выход

Как вы уже догадались, отправка POST-запроса на конечную точку выхода из системы уничтожает токен:

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

Ответ:

{
    "detail": "Successfully logged out."
}

Проверка электронной почты и сброс пароля

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

Настройки SMTP

Как упоминалось выше, некоторые конечные точки аутентификации зависят от отправки электронных писем (например, сброс пароля, проверка электронной почты и так далее). Чтобы Django мог отправлять электронные письма, необходимо настроить параметры SMTP.

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

Для целей тестирования вы можете использовать Console Email Backend от Django:

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

Этот бэкенд распечатывает письма в консоли вместо отправки реальных писем.

Вы можете использовать собственный SMTP-сервер или воспользоваться Brevo (ранее SendInBlue), Mailgun, SendGrid или аналогичным сервисом. Я предлагаю вам выбрать Brevo, поскольку они относительно дешевы и позволяют отправлять приличное количество писем ежедневно (бесплатно).

Чтобы настроить SMTP, добавьте следующее в core/settings.py:

# core/settings.py

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "<your email host>"                    # smtp-relay.sendinblue.com
EMAIL_USE_TLS = False                               # False
EMAIL_PORT = "<your email port>"                    # 587
EMAIL_HOST_USER = "<your email user>"               # your email address
EMAIL_HOST_PASSWORD = "<your email password>"       # your password
DEFAULT_FROM_EMAIL = "<your default from email>"    # email ending with @sendinblue.com

Код и конфигурация

Теперь, когда у нас настроен SMTP, мы можем сделать проверку электронной почты обязательной при регистрации и включить сброс пароля. Для этого добавьте следующие настройки django-allauth в core/settings.py:

# core/settings.py

ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"

Далее давайте позаботимся об URL-адресах, содержащихся в шаблонах писем confirmation и password reset. Замените {{ password_reset_url }} и {{ activate_url }} на следующие значения:

http://localhost:8000/api/auth/register/account-confirm-email/<str:key>/
http://localhost:8000/api/auth/password/reset/confirm/<str:uidb64>/<str:token>/

По умолчанию django-allauth заботится об этих URL. Он отображает форму и отправляет запрос обратно на бэкенд. Поскольку мы создаем API на основе REST, мы не хотим, чтобы это происходило; вместо этого мы хотим перенаправить эти два URL на наш фронтенд, откуда мы можем отправить ключи обратно на бэкенд.

В этом руководстве не представлено приложение на стороне клиента. URL http://localhost:3000 используется только в качестве заполнителя. Если вы решите протестировать приложение на собственном фронтенде, обязательно настройте django-cors-headers для обработки кросс-оригинальных запросов.

Чтобы настроить перенаправления, сначала определите следующие две настройки в core/settings.py:

# core/settings.py

# <EMAIL_CONFIRM_REDIRECT_BASE_URL>/<key>
EMAIL_CONFIRM_REDIRECT_BASE_URL = \
    "http://localhost:3000/email/confirm/"

# <PASSWORD_RESET_CONFIRM_REDIRECT_BASE_URL>/<uidb64>/<token>/
PASSWORD_RESET_CONFIRM_REDIRECT_BASE_URL = \
    "http://localhost:3000/password-reset/confirm/"

Убедитесь, что в конце URL-адресов стоит косая черта /.

Далее добавьте следующие два представления в authentication/views.py:

# authentication/views.py

from django.conf import settings
from django.http import HttpResponseRedirect


def email_confirm_redirect(request, key):
    return HttpResponseRedirect(
        f"{settings.EMAIL_CONFIRM_REDIRECT_BASE_URL}{key}/"
    )


def password_reset_confirm_redirect(request, uidb64, token):
    return HttpResponseRedirect(
        f"{settings.PASSWORD_RESET_CONFIRM_REDIRECT_BASE_URL}{uidb64}/{token}/"
    )

Последнее, зарегистрируйте вновь созданные представления в authentication/urls.py:

# authentication/urls.py

from dj_rest_auth.registration.views import (
    ResendEmailVerificationView,
    VerifyEmailView,
)
from dj_rest_auth.views import (
    PasswordResetConfirmView,
    PasswordResetView,
)
from authentication.views import email_confirm_redirect, password_reset_confirm_redirect
from dj_rest_auth.registration.views import RegisterView
from dj_rest_auth.views import LoginView, LogoutView, UserDetailsView
from django.urls import path


urlpatterns = [
    # ...
    path("register/verify-email/", VerifyEmailView.as_view(), name="rest_verify_email"),
    path("register/resend-email/", ResendEmailVerificationView.as_view(), name="rest_resend_email"),
    path("account-confirm-email/<str:key>/", email_confirm_redirect, name="account_confirm_email"),
    path("account-confirm-email/", VerifyEmailView.as_view(), name="account_email_verification_sent"),
    path("password/reset/", PasswordResetView.as_view(), name="rest_password_reset"),
    path(
        "password/reset/confirm/<str:uidb64>/<str:token>/",
        password_reset_confirm_redirect,
        name="password_reset_confirm",
    ),
    path("password/reset/confirm/", PasswordResetConfirmView.as_view(), name="password_reset_confirm"),
]

Не забудьте импортировать все необходимые представления из dj_rest_auth.

Отлично! Вот и все. Теперь у нас есть полностью рабочая система аутентификации с функцией проверки электронной почты и сброса пароля.

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

Давайте протестируем API.

Регистрация

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

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

Обязательно замените <your email address> на ваш реальный адрес электронной почты.

Ответ:

{
    "detail": "Verification e-mail sent."
}

После регистрации на вашу электронную почту будет отправлено письмо с подтверждением. Проверьте свой почтовый ящик:

Django Allauth Register Email

При нажатии на ссылку вы должны быть перенаправлены на ваш фронтенд:

http://localhost:3000/email/confirm/<key>

Проверить электронную почту

Затем вы можете отправить ключ обратно в бэкэнд, чтобы проверить адрес электронной почты:

$ curl -XPOST -H "Content-type: application/json" -d '{
      "key": "OQ:1ptSAe:gh_07-gQ_1ak6muKCAly..."
  }' 'http://localhost:8000/api/auth/register/verify-email/' | jq

Ответ:

{
    "detail": "ok"
}

После успешной проверки адреса электронной почты вы сможете войти в систему.

Сброс пароля

Чтобы запросить новый пароль, вам нужно отправить POST на адрес /api/auth/password/reset/ следующим образом:

$ curl -XPOST -H "Content-type: application/json" -d '{
      "email": "<your email address>"
  }' 'http://localhost:8000/api/auth/password/reset/' | jq

После отправки запроса вы получите аналогичное письмо:

Password Reset Email

При нажатии на ссылку вы будете перенаправлены на ваш фронтенд:

http://localhost:3000/password-reset/confirm/<uidb64>/<token>/

Оттуда вы можете использовать uid и token, чтобы изменить пароль:

$ curl -XPOST -H "Content-type: application/json" -d '{
      "uid": "7",
      "token": "bn8cnn-6a1bcbebf3a54cc48c064113e6b97d9f",
      "new_password1": "differentpassword123",
      "new_password2": "differentpassword123",
  }' 'http://localhost:8000/api/auth/password/reset/' | jq

Аутентификация по социальным сетям

В этом разделе мы рассмотрим, как настроить социальную аутентификацию. Я проведу вас через процесс настройки социальной аутентификации в 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 авторизованного перенаправления. Для упрощения тестирования я рекомендую также добавить:

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

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

Двигаясь дальше, вернитесь в свой проект 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://localhost:3000/"
    client_class = OAuth2Client

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

# authentication/urls.py

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

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

Вот и все!

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

Чтобы проверить, работает ли конечная точка 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/'

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

{
    "key": "3a67e32d6fbc043b29b64fafc6f79c5e94af94a9"
}

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

Заключение

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

Окончательный исходный код доступен на репо django-rest-allauth GitHub.

Будущие шаги

  1. Рассмотрите возможность замены аутентификации с помощью токенов на djangorestframework-simplejwt или django-rest-knox. Они предоставляют дополнительные возможности настройки и являются более безопасными.
  2. Чтобы добавить другого социального провайдера, например Twitter или GitHub, загляните в dj-rest-auth docs.
  3. Чтобы включить функцию привязки социальных аккаунтов, загляните в "Social Connect Views".
  4. Для использования бэкенда с фронтендом на базе JavaScript вам, скорее всего, потребуется настроить CORS-заголовки с помощью django-cors-headers.
Вернуться на верх