[PYTHON] Implementation of custom user model authentication in Django REST Framework with djoser

What is djoser

djoser is a library that supports basic user authentication and registration on the Django REST Framework. It can also be used for custom models, and is designed for an architecture that fits better with a Single Page Application (SPA) rather than reusing Django's code.

This time I will write about the implementation of the simplest authentication function of djoser. Please note that this authentication should not be actually used for security reasons, and there are stronger security settings such as the JWT authentication below. I will introduce it as a simple authentication to the last.

The source code is here

All of the following can be used as endpoints after installation.

/users/ /users/me/ /users/confirm/ /users/resend_activation/ /users/set_password/ /users/reset_password/ /users/reset_password_confirm/ /users/set_username/ /users/reset_username/ /users/reset_username_confirm/ /token/login/ (Token Based Authentication) /token/logout/ (Token Based Authentication) /jwt/create/ (JSON Web Token Authentication) /jwt/refresh/ (JSON Web Token Authentication) /jwt/verify/ (JSON Web Token Authentication) Getting started

I have written other articles related to djoser.

-Implementation of authentication function in Django REST Framework using djoser -Implementation of JWT authentication function in Django REST Framework using djoser

This time, we will implement user authentication using JWT authentication.

How to use

The flow up to the middle is the same as the JWT authentication time, and the major changes are writing models.py by yourself and increasing some settings.py.

$ pip install -U djoser

Since JWT authentication is used, you need to use simple_jwt as well.

$ pip install -U djangorestframework_simplejwt

First, make a project,

$ django-admin startproject djoser_customuser

Go inside the project and

$ cd djoser_customuser

Create an app.

$ django-admin startapp accounts

Next, let's set up Django.

setings.py



from datetime import timedelta # add

   .........

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # add
    'djoser', # add
    'accounts' # add
]

# add
SIMPLE_JWT = {
    #Set token to JWT
    'AUTH_HEADER_TYPES':('JWT'),
    #Token duration setting
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60)
}

# add
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
}

# add
AUTH_USER_MODEL = 'user.accounts'

urls.py


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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/auth/',include('djoser.urls')), #add
    path('api/auth/',include('djoser.urls.jwt')), #add
]

From here, I will write a custom user model for the part that is significantly different from the JWT authentication article. Regarding why we use a custom user model instead of the default one, in general, the default user model is not enough to create the web application you want, and development has progressed. Since it is difficult to modify the user model later, it is desirable to design a custom user model that meets the requirements in advance.

In addition, Django comes with two classes, the "AbstractUser" class and the "AbstractBaseUser", which come by default.

Since it is similar to the User class, it is easy to use because it requires less coding. Therefore, it is often used when only adding attributes.

It requires more coding because you have to write the user name and other attributes, but it is more flexible than AbstractUser.

Generally, it has the above characteristics. This time, we will use "AbstactBaseUser".

models.py


from django.db import models

# add
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.utils import timezone
from django.core.mail import send_mail
from django.contrib.auth.base_user import BaseUserManager

# add
class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, username, email, password, **extra_fields):
        """
        Create and save a user with the given username, email, and password.
        """
        if not username:
            raise ValueError('The given username must be set')
        email = self.normalize_email(email)
        username = self.model.normalize_username(username)
        user = self.model(username=username, email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(username, email, password, **extra_fields)

    def create_superuser(self, username, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(username, email, password, **extra_fields)

class User(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.
    Username and password are required. Other fields are optional.
    """
    username_validator = UnicodeUsernameValidator()

    username = models.CharField(
        _('username'),
        max_length=150,
        unique=True,
        help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
        validators=[username_validator],
        error_messages={
            'unique': _("A user with that username already exists."),
        },
    )
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)
    email = models.EmailField(_('email address'), blank=True,unique=True)
    profile = models.CharField(_('profile'), max_length=255, blank=True) # add
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)


    objects = UserManager()

    EMAIL_FIELD = 'email' # fix
    USERNAME_FIELD = 'email' # fix
    REQUIRED_FIELDS = ['username'] # fix

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def get_full_name(self):
        """
        Return the first_name plus the last_name, with a space in between.
        """
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        """Return the short name for the user."""
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)

After that, the flow is exactly the same as the JWT article. However, there is a difference, first in models.py

models.py


EMAIL_FIELD = 'email' # fix
USERNAME_FIELD = 'email' # fix
REQUIRED_FIELDS = ['username'] # fix

Until now, the user name was a required item when creating a user, but this time Email is a required item. When actually using some kind of Web service, email should be a required item rather than username. These are also the advantages of using custom users.

Migrate and create superuser.

$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py createsuperuser
Email address: [email protected]
Username: Admin
Password:*********** 

Start on localhost and specify http://localhost:8000/api/auth To access.

$ python manage.py runserver

スクリーンショット 2020-04-21 0.41.16.png

And http://localhost:8000/api/auth/users/ When you access

スクリーンショット 2020-04-21 0.41.25.png

User information cannot be obtained because it is still not authenticated. So, like last time, to get a token http://localhost:8000/api/auth/jwt/create/ Access, enter the value of the superuser you registered earlier, and press POST at the bottom right.

スクリーンショット 2020-04-21 0.41.41.png

Then, the following token will be issued, and the token of access at the bottom will be copied.

スクリーンショット 2020-04-21 0.42.10.png

Let's actually get the user information using the curl command.

curl -LX GET http://127.0.0.1:8000/api/auth/users/me/ -H 'Authorization: JWT xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

(Enter the token in xxxx)

Then

{"username":"Admin","id":1,"email":"[email protected]"}

The user information you registered earlier will be returned.

Recommended Posts

Implementation of custom user model authentication in Django REST Framework with djoser
Implementing authentication in Django REST Framework with djoser
Implementation of JWT authentication functionality in Django REST Framework using djoser
More new user authentication methods with Django REST Framework
Create APIs around user authentication with Django REST Framework
Implement a Custom User Model in Django
Implementation of CRUD using REST API with Python + Django Rest framework + igGrid
How to deal with garbled characters in json of Django REST Framework
Implement hierarchical URLs with drf-nested-routers in Django REST framework
Django REST framework with Vue.js
Login with django rest framework
How to write custom validations in the Django REST Framework
Implementation of login function in Django
[Django] Use MessagePack with Django REST framework
Create RESTful APIs with Django Rest Framework
Logical deletion in Django, DRF (Django REST Framework)
Understand the benefits of the Django Rest Framework
CRUD GET with Nuxt & Django REST Framework ②
CRUD POST with Nuxt & Django REST Framework
CRUD GET with Nuxt & Django REST Framework ①
CRUD PUT, DELETE with Nuxt & Django REST Framework
I want to create an API that returns a model with a recursive relationship in the Django REST Framework
Implement JWT login functionality in Django REST framework
How to use fixture in Django to populate sample data associated with a user model
Create a Todo app with Django REST Framework + Angular
Maximum likelihood estimation implementation of topic model in python
Create a Todo app with the Django REST framework
When you want to filter with Django REST framework
List method for nested resources in Django REST framework
Variational Bayesian inference implementation of topic model in python
Model changes in Django
Django REST framework basics
Django Rest Framework Tips
[Django] Manage settings like writing in settings.py with a model
Easy implementation of credit card payment function with PAY.JP [Django]
Create an authentication feature with django-allauth and CustomUser in Django
Django REST framework stumbling block
Implementation of quicksort in Python
Crawling with Python and Twitter API 2-Implementation of user search function
Create a REST API to operate dynamodb with the Django REST Framework