Evgenii Legotckoi
Evgenii LegotckoiFeb. 14, 2022, 3:24 a.m.

Django Rest Framework - Tutorial 001. Adding Token Authentication

At the moment I am actively working on an application that will work with the REST API of a Django site. And one of the first steps was to set up user authentication by token, but in order for this to work, you must first obtain an authorization token.

Let's look at how this can be done.


Install Django Rest Framework

Since this is the very first article on using DRF, I will describe in more detail the process of installing and configuring DRF. In what follows, I will no longer give these instructions.

Install DRF and other necessary packages

pip install djangorestframework
pip install markdown       # Markdown support for the browsable API.
pip install django-filter

Next, add the application 'rest_frameword' to INSTALLED_APPS in the site configuration file settings.py

INSTALLED_APPS = [
    ...
    'rest_framework',
]

Further, the official documentation suggests including the login and logout View from rest_framework in the urls.py file, so that you can conveniently use the API in the browser.

To be honest, I don’t use this API, because it’s more convenient for me to use the documentation generated through swagger, which I will also talk about later, but I still connect the View data so that I don’t solve random problems later.

urlpatterns = [
    ...
    path('api-auth/', include('rest_framework.urls')),
]

Next, configure the default authentication and permission classes (also in settingsp.py)

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

Now everything is ready in order to write an access point to receive a token. But first I want to connect the generation of swagger documentation. It’s just more convenient to work this way, for example, I really like the redoc documentation, in which everything is displayed quite beautifully and conveniently.

Installing Swagger

And now we will immediately connect swagger on the site. I understand that this is not related to the topic of the article, but in the code I do special manipulations to enable a special documentation display and I think that this may be useful to you in the future, because:

  • You will use swagger in the future
  • You will sometimes need to customize the output of information in the API description, namely for the token I had the most specific change for swagger.

Установим drf-yasg

To do this, following the documentation, run the following command

pip install -U drf-yasg

Then we modify the settings.py file

INSTALLED_APPS = [
   ...
   'django.contrib.staticfiles',  # required for serving swagger ui's css/js files
   'drf_yasg',
   ...
]

And now let's register swagger in the site urls

from django.urls import re_path
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions

schema_view = get_schema_view(
   openapi.Info(
      title="EXAMPLE API",
      default_version='v1',
      description="REST API Documentation",
      contact=openapi.Contact(email="example@example.com"),
   ),
   public=True,
   permission_classes=(permissions.IsAdminUser,),
)

urlpatterns = [
    ...
    re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
    re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
    re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]

If you take a close look at the code, you will see that I registered the swagger only for the administration of the site. If you want the documentation to be open, then remove this line

permission_classes=(permissions.IsAdminUser,),

Optionally, you can add information to the documentation output about which authentication types are used for the API. This is done in the settings.py file.

SWAGGER_SETTINGS = {
   'SECURITY_DEFINITIONS': {
      'Basic': {
            'type': 'basic'
      },
      'Bearer': {
            'type': 'apiKey',
            'name': 'Authorization',
            'in': 'header'
      }
   }
}

Now you can start implementing an access point to get a token.

Api application

Let's create an api application in which an endpoint will be added to receive a token.

python manage.py startapp api

serializers.py

Next, we will create a file with serializers, in which a special serializer will be created that will describe the site's response in swagger.

# -*- coding: utf-8 -*-

from rest_framework import serializers


class Response201AuthTokenSerializer(serializers.Serializer):
    token = serializers.CharField(required=True, allow_blank=False)
    user_id = serializers.IntegerField(required=True)

This serializer will correctly generate the response information in the documentation, and I use it to generate a JSON response with a token.

views.py

Then we modify the views.py file to get the endpoint.

# -*- coding: utf-8 -*-

from django.contrib.auth import get_user_model
from django.http import JsonResponse
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import authentication, viewsets
from rest_framework.authtoken.views import ObtainAuthToken

from api.serializers import Response201AuthTokenSerializer


class CustomAuthToken(ObtainAuthToken):
    authentication_classes = [authentication.BasicAuthentication]

    @swagger_auto_schema(responses={
        "201": openapi.Response(
            description=_("User has got Token"),
            schema=Response201AuthTokenSerializer,
        )
    })
    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data,
                                           context={'request': request})
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
        response201 = Response201AuthTokenSerializer(data={"token": token.key, "user_id": user.pk})
        response201.is_valid(raise_exception=True)
        return JsonResponse(response201.data)

As you can see in the code there is a custom swagger schema for the 201 response, the content has been created. The correct documentation will be generated from the Response201AuthTokenSerializer serializer.
Among other things, I use the Response201AuthTokenSerializer to form a response with a token as well as a user id. In the future, I use the user id to manipulate data in a mobile application.
A distinctive nuance of this view is that only BasicAuthentication is used as authentication, that is, the user gets access to this view only through username and password. In the future, API development is underway to use the token.

urls.py

And now connect the view to get the token in the urls files

# -*- coding: utf-8 -*-

from django.urls import path

from api import views


app_name = 'api'
urlpatterns = [
    path('api-token-auth/', views.CustomAuthToken.as_view()),
]

In this file, we define the name of the application that is used to generate routes, that is, api. We also connect the view of the token to the routing.

And at the end, we include the urls.py of the api application in the main urls.py file of the entire project.

urlpatterns = [
    ...
    path('api/', include('api.urls')),
    ...
]

Now, in order to get a token, you need to send a POST request to the url http://127.0.0.1/api/api-token-auth/ , if you want to test the functionality on a local server. In the case of a combat server, you need to send a request to the address with the domain of your site.

For authentication, you need to use Basic authentication.

Example of token request in Felgo QML

Here is an example code for requesting a token in an application written in QML Felgo

function login(username, password, success, error) {
    HttpRequest
    .post("http://127.0.0.1/api/api-token-auth/", {username: username, password: password})
    .timeout(5000)
    .set('Content-Type', 'application/json')
    .then(function(res) { success(res) })
    .catch(function(err) { error(err) });
}
We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

BL4CK R4BBIT
  • Feb. 14, 2022, 5:42 a.m.

Доброго времени.
А если кастомный юзер.
И активация юзера подтверждением по смс или звонку?

Evgenii Legotckoi
  • Feb. 14, 2022, 5:54 a.m.
  • (edited)

Добрый день

Если Вы имеетe ввиду, что модель пользователя наследована от AbstractUser и переопределена для проекта, то да - этот пример рабочий, я сейчас занимаюсь одним проектом, который будет иметь приложение и использую там кастомного пользователя.

А что касается подтверждения по СМС, то я участвовал несколько лет назад в проекте, где было подтверждение по СМС. Код показать не могу, но суть была в том, что там было два REST API View, сначало приложение стучалось на первый url, куда посылало логин и пароль, при получении ответа 200, приложение меняло форму ввода данных и ждало, пока пользователь заполнит код из СМС и подтвердит отправку. В этом случае отправка кода была уже на второй url, который проверял правильность кода, и уже в этом случае отдавал токен.

Естественно первый URL, который обрабатывал логин и пароль, стучался на API стороннего провайдера по отправке СМС, получал отправленный код и сохранял в отдельную таблицу, чтобы потом сравнить код и соответствие пользователя коду.

BL4CK R4BBIT
  • Feb. 14, 2022, 6:14 a.m.
  • (edited)

В моем случае кастом от AbstractBaseUser

class User(AbstractBaseUser):
    phone       = PhoneNumberField(region='RU', unique=True)
    username    = models.CharField(max_length = 150, blank = True, null = True)
    standard    = models.CharField(max_length = 3, blank = True, null = True)
    score       = models.IntegerField(default = 16)
    first_login = models.BooleanField(default=False)
    active      = models.BooleanField(default=False)
    staff       = models.BooleanField(default=False)
    admin       = models.BooleanField(default=False)
    timestamp   = models.DateTimeField(auto_now_add=True)

    USERNAME_FIELD = 'phone'
    REQUIRED_FIELDS = []

    objects = UserManager()

    def __str__(self):
        return self.phone

    def get_full_name(self):
        return self.phone

    def get_short_name(self):
        return self.phone

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):

        return True

    @property
    def is_staff(self):
        return self.staff

    @property
    def is_admin(self):
        return self.admin

    @property
    def is_active(self):
        return self.active

Только токен я использую knox(Стандарт мобильной безопастности Samsung), потому и спросил

Дальше понятно:
Регистрация post на страницу урл api/register(можно сразу отправить запрос на звонок, после создания пользователя)
Если удачно то получим 4-x символьный код от провайдера, запищем его. Если неудачно то создаем свой и шлем смс
Cледом post-запрос на верификацию с кодом(при удаче - активируем пользователя, при не удаче 4 попытки на повтор)

Evgenii Legotckoi
  • Feb. 14, 2022, 6:22 a.m.

В таком случае без напильника этот код сходу не взлетит, если конкретно knox нужно использовать.
А касательно самого AbstractBaseUser, то должно заработать.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Last comments
k
kmssrFeb. 8, 2024, 6:43 p.m.
Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lesson 007. Working with ICMP Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVADec. 25, 2023, 10:30 a.m.
Boost - static linking in CMake project under Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJoDec. 25, 2023, 8:38 a.m.
Boost - static linking in CMake project under Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
GvozdikDec. 18, 2023, 9:01 p.m.
Qt/C++ - Lesson 056. Connecting the Boost library in Qt for MinGW and MSVC compilers Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Now discuss on the forum
AC
Alexandru CodreanuJan. 19, 2024, 11:57 a.m.
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCTDec. 27, 2023, 8:57 a.m.
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
ДмитрийJan. 10, 2024, 4:18 a.m.
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii LegotckoiDec. 12, 2023, 6:48 a.m.
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

Follow us in social networks