Evgenij LegotskojFeb. 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.
- company blog
Support the author Donate

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

Добрый день

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

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

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

В моем случае кастом от 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 попытки на повтор)

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Fornex

Let me recommend you a great European Fornex hosting.

Fornex has proven itself to be a stable host over the years.

For Django projects I recommend VPS hosting

Following the link you will receive a 5% discount on shared hosting services, dedicated servers, VPS and VPN

View Hosting
YL

C++ - Test 001. The first program and data types

  • Result:66points,
  • Rating points-1
k
  • korsar
  • Nov. 23, 2022, 8:57 a.m.

C++ - Test 005. Structures and Classes

  • Result:50points,
  • Rating points-4
TM

C++ - Тест 003. Условия и циклы

  • Result:78points,
  • Rating points2
Last comments
b

GameDev on Qt - Tutorial 5. The explosion of bullets using sprite images

Если вдруг кто-то прочитает.... Скачал проект, скомпилил, запустил. Всё красиво и объектно ориентировано, но вот FPS дико страдает, когда появляется 10+ врагов. Может есть какие-то надстрой…
  • juvf
  • Nov. 25, 2022, 12:14 a.m.

Qt/C++ - Lesson 051. QMediaPlayer – simple audio player

Добрый день. Подскажите, как можно перехватить в Qt или Qml уровень воспроизводимого звука? Т.е. требуется сделать виртуальный винтажный индикатор (стрелочный или светодиодный), который бы …
  • juvf
  • Nov. 3, 2022, 3:20 a.m.

QML - Lesson 007. ListView. Dynamic creation and deletion of elements

Добрый день. Очень полезная статья. Спасибо. Вопрос такой: 1) нужно "взять" кнопку 2 пальцем (прикаснулись пальцем к кнопке 2, держим, через 2-3 кнопка оторвалась от ListView) и пере…

PyQt5 - Lesson 007. Works with QML QtQuick (Signals and slots)

А можно ли из QML сделать привязку свойства к свойству пайтоновского объекта? Ну, т.е. , например, у нашего объекта Calculator обхвялем свойства sumresult и subresult c с декоратором @pyqtProp…
MA

Python Image Recognition with TensorFlow and Keras

А что собственно выводит программа, как вывести то что она смогла распознать?
Now discuss on the forum
AB

Sorting the added QML elements in the ListModel

I am writing an alarm clock in QML, I am required to sort the alarms in ascending order (depending on the date or time (if there are several alarms on the same day). I've done the sorting …
AM

Как добавить в скрипт размер каждого файла в Мб ?

IDLE (Python 3.10 64-bit) Win 10 Есть такой скрипт: Поиск перечня файлов в папке и запись списка: import ospath = 'E:\Мой Python\__Видеокурсы Python'rez = sorted(os.listdir(pa…
n
  • nkly
  • Oct. 18, 2022, 9:14 a.m.

Как сохранить данные древовидной модели на основе QStandardItemModel в файл

Вы меня неправильно поняли. Допустим я собираю кулинарные рецепты и один из них - рецепт супа Харчо. Структура файла данных такова: { node:Рецепт супа Харчо parent:Вкусные блюда, …

Вопрос по Qt Creator

Добрый день. Не знаю, подобную проблему я не решал.

Задать другой класс div-у

Добрый день. Попробуйте использовать Selenium. Это библиотека есть в виде Python модуля и она позволяет загружать страницу и манипулировать html элементами. Как я понимаю, в ней можно…
About
Services
© EVILEG 2015-2022
Recommend hosting TIMEWEB