The tutorial has been split into two parts- setting up the backend, and setting up the frontend.
The repo has two branches: part-1
and part-2
. part-1
contains the files for this tutorial, part-2
contains
the files for this tutorial and the next.
Tutorial Part 2: Here
GITHUB REPO: https://github.com/IgnisDa/django-nuxtjs-authentication
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.
Prerequisites
- Familiarity with django-rest-framework
- Knowledge of nuxt-auth: this video will be enough
Setting up the backend
Install the following packages in your virtual environment:
- django
- djangorestframework
- djoser
- django-cors-headers
- 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
.
# backend/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
]
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
.
# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _
from . import managers # we will write this file 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"
# accounts/managers.py
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.
# backend/settings.py
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',
]
}
Then, we set up basic urls.
# backend/urls.py
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.
Next we set up serializers for our CustomUser
model.
# accounts/serializers.py
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')
Then we define the views and viewsets that will use these serializers to deliver the data to the frontend.
# accounts/views.py
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
# accounts/viewsets.py
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()
# accounts/routers.py
from rest_framework import routers
from . import viewsets
router = routers.DefaultRouter()
router.register('', viewsets.CustomUserModelViewSet)
# accounts/urls.py
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)),
]
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. |
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 admin.
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.
# backend/settings.py
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.
Tutorial Part 2: Here
Top comments (0)