Authentication with Django and Nuxt (the backend)
Introduction
We’ll be using “token authentication” using the djoser package to implement an authentication backend API, and consume it with a Nuxtjs frontend.
NOTE: For the sake of brevity, I will omit all comments explaining the working. However, the code is well commented and can be accessed via the github repository.
Pre-requisites
- Familiarity with
django-rest-framework - Knowledge of the
nuxt-authmodule: this video will be enough
Setting up the backend
Install the following packages in your virtual environment:
pip install django djangorestframework djoser httpie validate-email
Start by making a common directory nuxtjs+drf-user-auth/ where the frontend (nuxtjs) and
backend (django rest framework) will live separately in frontend/ and backend/
directories respectively.
mkdir nuxtjs+drf-user-auth && cd nuxtjs+drf-user-auth/
django-admin startproject backend && cd backend/
python manage.py startapp accounts
Add the new app and addons to settings.py.
INSTALLED_APPS = [
# Other stuff
'rest_framework',
'rest_framework.authtoken',
'corsheaders',
'accounts.apps.AccountsConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
# Other stuff
]
Models for authentication
Next, we define a custom user model that will handle user authentication for us. This will
allow us to add more fields to the user model than what the default
django.contrib.auth.models.User provides, and also use email as the default identifier
instead of username.
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext as _
from . import managers ## we will write this module shortly
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
bio = models.TextField()
gender = models.CharField(
max_length=140,
null=True,
choices=(
('Male', 'Male'),
('Female', 'Female'),
('Other', 'Other')
)
)
birth_date = models.DateField(null=True, blank=True)
pro = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = managers.CustomUserManager()
def __str__(self):
return f"{self.email}'s custom account"
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import gettext_lazy as _
from validate_email import validate_email
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, **extra_fields):
if not email:
raise ValueError(_('The Email must be set'))
email = self.normalize_email(email)
if not validate_email(email):
raise ValueError(_('Invalid email set'))
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', 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(email, password, **extra_fields)
Add the relevant settings in your settings.py file.
AUTH_USER_MODEL = 'accounts.CustomUser'
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
Routing
Then, we set up basic urls.
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('accounts/', include('accounts.urls')),
path('', include('djoser.urls.authtoken')),
path('admin/', admin.site.urls),
]
Run makemigrations, migrate, and create a new superuser using
createsuperuser. Fire up a development server using runserver, and
then visit token/login/ in your browser. Enter the correct credentials
and check that you get a token as a response in the auth_token
property. Also visit token/logout/, though it will be unusable.
Serialization
Next we set up serializers for our CustomUser model.
from django.contrib.auth import get_user_model
from rest_framework import serializers, validators
CustomUser = get_user_model()
class CustomUserSerializer(serializers.ModelSerializer):
email = serializers.CharField(
write_only=True, validators=[validators.UniqueValidator(
message='This email already exists',
queryset=CustomUser.objects.all()
)]
)
password = serializers.CharField(write_only=True)
birth_date = serializers.CharField(required=False)
bio = serializers.CharField(required=False)
gender = serializers.CharField(required=False)
last_name = serializers.CharField(required=False)
first_name = serializers.CharField(required=False)
birth_date = serializers.CharField(required=False)
class Meta:
model = CustomUser
fields = ('first_name', 'last_name', 'email',
'password', 'bio', 'gender', 'birth_date')
class CustomUserRetrieveSerializer(serializers.ModelSerializer):
birth_date = serializers.CharField(required=False)
bio = serializers.CharField(required=False)
gender = serializers.CharField(required=False)
class Meta:
model = CustomUser
fields = ('first_name', 'last_name', 'email',
'bio', 'gender', 'birth_date', 'id')
Viewsets to make API calls to
Then we define the views and viewsets that will use these serializers to deliver the data to the frontend.
from django.contrib.auth import get_user_model
from rest_framework import generics, permissions
from . import serializers
CustomUser = get_user_model()
class UserRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = CustomUser.objects.all()
serializer_class = serializers.CustomUserRetrieveSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_object(self):
return self.request.user
from django.contrib.auth import get_user_model
from rest_framework import permissions, viewsets
from . import serializers
CustomUser = get_user_model()
class CustomUserModelViewSet(viewsets.ModelViewSet):
serializer_class = serializers.CustomUserSerializer
permission_classes = (permissions.AllowAny,)
queryset = CustomUser.objects.all()
def perform_create(self, serializer):
instance = serializer.save()
instance.set_password(instance.password)
instance.save()
from rest_framework import routers
from . import viewsets
router = routers.DefaultRouter()
router.register('', viewsets.CustomUserModelViewSet)
from django.urls import include, path
from . import routers, views
urlpatterns = [
path('data/', views.UserRetrieveUpdateDestroyAPIView.as_view(),
name='user-data'),
path('users/', include(routers.router.urls)),
]
Overview of our new API
The new urls will expose a few endpoints for the following purposes:
| METHOD | PATH | PURPOSE |
|---|---|---|
| GET | accounts/data/ | Retrieve data of the currently logged in user. Anonymous users can not access this page. |
| GET | accounts/users/ | A list of all the users in the database. |
| POST | accounts/users/ | Create a new user using the data that accompanies the POST request. |
| PATCH/ PUT | accounts/users/<pk>/ | Update, or change all user details for user with this pk. |
| POST | token/login/ | Send a POST request with the correct credentials and this will respond with a login token called auth_token. |
| POST | token/logout/ | Send a POST request with the auth_token in the header and the corresponding user will be logged out. |
Testing our backend
Let us use httpie to check whether
our endpoints work.
# Register a new user
$ http POST http://127.0.0.1:8000/accounts/users/ email='email1@email.com' password="test-pass" first_name="Dabreil" last_name="Ignis"
{
"bio": "",
"birth_date": null,
"first_name": "Dabreil",
"gender": null,
"last_name": "Ignis"
}
# Login using the new user
# You can visit `admin/authtoken/token/` to see the new token generated
$ http POST http://127.0.0.1:8000/token/login/ email='email1@email.com' password="test-pass"
{
"auth_token": "b1a73afd0431c87b5e0c4afb4b085d401d652edb"
}
# Access data using the token generated. Make sure you use the correct token that you got in the above step
$ http GET http://127.0.0.1:8000/accounts/data/ "Authorization: Token b1a73afd0431c87b5e0c4afb4b085d401d652edb"
{
"bio": "",
"birth_date": null,
"email": "email1@email.com",
"first_name": "Dabreil",
"gender": null,
"id": 3,
"last_name": "Ignis"
}
# Logout the user
$ http POST http://127.0.0.1:8000/token/logout/ 'Authorization: Token b1a73afd0431c87b5e0c4afb4b085d401d652edb'
# You won't get a response but the token will be deleted from the database. Check this from the database.
Yay! It all works! Now we only need to add a few settings
to make sure that our frontend can communicate with
our backend using the django-corsheaders package.
CORS_ORIGIN_WHITELIST = ('http://127.0.0.1:3000', 'http://localhost:3000')
This is the default development server that Nuxtjs uses. You can configure yours accordingly. That’s it for this part.
Make sure you checkout the Part-2 to learn how to add authentication to your Nuxtjs frontend.