Create a RESTFul API with Users, JWT Authentication and permission control using Django 1.11 & DRF — Part 1

Andreas Pogiatzis
8 min readJul 27, 2018

--

Source: Unsplash — Credit: @cdr6934

Hey folks! Today I have a very practical post for you to exercise your coding skills a little nit and get a feel of how DRF works in practice.

I am pretty sure all of you had at least one idea that you wanted (or still want) to make it happen. Either to launch a startup… or for fun … or because why not?! Whatever sick reason you had in your mind, you would most likely encounter the scenario where you would need to create a user model and provide login/registration functionality using some sort of authentication mechanism.

For that purpose, in this post I will attempt to give you a walkthrough on how to develop a RESTFul API which implements JWT Authentication and permission control using Django 1.11 and DRF. Personally, I believe that this combination is one of the fastest, most robust and straightforward way to having a RESTFul API up and running.

Follow along with the steps below and you will be amazed by how easy it is to set this up. I also have all the code for this tutorial in my Github repo where you can clone it directly and play with it. Make sure you install the dependencies by running pip install -r requirements.txt if you clone the repository.

Django + DRF Logo

For this tutorial I assume that you are comfortable with developing Python code, installing development environment and creating/activating a virtual environment. Also for transparency reasons, the Python version used is 3.6.0 and DRF 3.8.2.

It is important that you use Python 3.6 rather than 3.7 because Django 1.11 has a tiny issue with Python 3.7

Requirements

I have listed the scope of what we are building below, so that I can save you some time reading, in case this tutorial is not what you were looking for:

  1. Create custom user model that is extended from Django’s base user model
  2. Register custom user model to admin panel
  3. Create API endpoint for registering new users
  4. Create API endpoint for user login ( Use email for login )
  5. Create API endpoint for listing all users
  6. Create API endpoint for updating a user
  7. Create API endpoint for deleting a user
  8. Create API endpoint to login with email/password
  9. Use JWT for Authentication
  10. Use custom permissions to control API endpoint access (Admin has full control/Logged in user can access only itself)

Setup environment and create project

First things first, let us create a virtual environment, install Django and create a new project.

Open up a new terminal and navigate to a folder that you want to create the project. Then run:

virtualenv --python=python3 django-env
source django-env/bin/activate
pip install django==1.11

Now with Django 1.11 installed. Create a project called restapi by executing:

mkdir restapi
django-admin startproject config .

Wait a minute… the project is called config? Not really! This may look a bit counterintuitive at first but I really prefer this pattern for creating a new django project because otherwise the inner folder which contains the configuration of the project would have been called restapi as well.

Navigate to the restapi folder and create a new app:

cd restapi
django-admin startapp api

If everything went well you should have a new folder called restapi with the following structure:

restapi
|--- manage.py
|--- api
|--- migrations
|--- __init__.py
|--- admin.py
|--- apps.py
|--- models.py
|--- tests.py
|--- views.py
|--- config
|--- __init__.py
|--- settings.py
|--- urls.py
|--- wsgi.py

Now open the project in your favorite IDE and edit the INSTALLED_APPS variable in the settings.py file so that it includes our newly created app. Like this:

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api', # Add this line here.
]

Create custom user models

This part here may be a little bit tricky, so I will spend some time on a brief overview of how Django handles user models in order to get a better understanding of what we will be doing throughout the next sections.

Background

Since Django comes bundled with a ton of stuff that include authentication, session middleware etc.. it also provides a built in basic user model. This is good for very basic use cases, but most of the times the user model has to be extended in order to cover the requirements.

More precisely the default user model only provides the following fields:

- email
- username
- password
- first_name
- last_name

Plus some additional fields that there wasn’t any need to include here but you can find the complete definition here.

Now, there are three distinct ways to extend the default user model to fit your needs.

1. Subclass Django’s AbstractBaseUser model

This will give you full control over the User model but it requires more work and extra care when developing because things can easily go wrong. This option is mostly used when it is necessary to add a custom authentication backend. In this tutorial we will not be using this method but feel free to read more about it here.

2. Subclass Django’s AbstractUser model

This option still provides enough flexibility but keeps the default user fields. It can be used when you need to mess with the existing authentication functionality like (i.e. using email for authentication/add fields for authentication).

Since we will be using email as a username for authentication, we will be using this method to overwrite the exiting Django behavior.

3. Add OneToOne relationship with a user profile

This is probably the easiest and most widely used method for extending the default user model. It keeps the existing functionality and adds whatever needs to be added on top of it. This is the choice to go if you are just seeking to add some extra fields on the user model.

Again, we will be implementing that option as well in order to add some additional fields to the current model.

Get down to business — Define the models

Go ahead and extend the AbstractUser model by adding the following code snipped in the api/models.py file:

from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import ugettext_lazy as _
from django.conf import settings

class User(AbstractUser):
username = models.CharField(blank=True, null=True)
email = models.EmailField(_('email address'), unique=True)

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']

def __str__(self):
return "{}".format(self.email)
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profile')
title = models.CharField(max_length=5)
dob = models.DateField()
address = models.CharField(max_length=255)
country = models.CharField(max_length=50)
city = models.CharField(max_length=50)
zip = models.CharField(max_length=5)
photo = models.ImageField(upload_to='uploads', blank=True)

Now in order to let Django know that it should use our extended model add the following line to config/settings.py file:

AUTH_USER_MODEL = 'api.User'

Let’s take a step back and explain what we did here. First of all we subclassed AbstractUser (Option 1) so that the email field is used as a username for authentication. More particularly, that was achieved by setting the USERNAME_FIELD as the email.

You may be wondering why we redefined the email and username fields since they are already supposed to be there by the AbstractUser class. There are two fundamental reasons for that: Firstly ,we wanted to override the existing email field so that we can set the unique parameter as True. Otherwise, an exception would have been raised because USERNAME_FIELD requires that is unique. Secondly, the username field has to be overridden in order to set the null and blank parameters as True and thus making it trivial to set. Failing to do so, would require us to add a username for every new user which is clearly not desirable.

Plus, the REQUIRED_FIELDS array specified the fields that are required by the createsuperuser command. The username has to be in that list because otherwise Django complains when creating super users.

Continuously, we created a UserProfile model which holds the extra user fields and has an one to one relationship with the user model that we defined earlier.

Note that settings.AUTH_USER_MODEL is used to reference the user model so that any reusable apps that also implement a custom user model don’t interfere with our setup. Although this is not the case, it worths picking up good practices.

Furthermore, because we introduced an ImageField it is necessary to install the pillow python package. To do so run:

pip install pillow

Lastly the underscore function (from django.utils.translation import ugettext_lazy as _) is just a translation hook to make the project translatable. No need to worry about it here. We could have not used it at all, but as we said… good practices!

Register user model to admin panel

We got over the hassle of extending the user model. Cool! Now let’s register this model into Django’s admin panel so we can add/delete/update some users.

Add the following code to the api/admin.py file in the project:

There are few things to explain here. The UserProfileInline class defines an Inline object for the admin panel. That is, an object that it is displayed as part of another object “inline”, hence the name. Since we want the profile to be part of the User model we defined it as an Inline object and added it to the inlines tuple of theUserAdmin class.

The can_delete flag specified whether the inline object can be deleted through the other object or not. Still not that important, but it is preferable not to.

As you might have guessed, the UserAdmin class is just a subclass of Django’s default UserAdmin class (It was imported as BaseUserAdmin to avoid the name conflict). Here we overridden the fields that contained the username field so that we can get rid of it.

Everything has been good so far but we still haven’t witnessed any solid result. Now is the time for it! Let’s apply the migrations and create a superuser by running:

python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

Fill the super user fields when prompted. Username can be anything, doesn’t really matter

Navigate to the admin panel at http://localhost:8000/admin , login, click ‘User’, click ‘Add User’ on the top right corner and voila!

Add user — Django admin panel.

You can create and store instances of your awesome extended user model!

Next steps!

Congratulations on your progress! Told you it wasn’t going to be hard! Hope you enjoyed it so far!

The next steps are to integrate DRF, create RESTFul API to expose the user model and add support for login using JWT tokens.

Personally, I prefer to keep posts less than 10 min reads so you can find the remaining of the steps in the Part 2 of the tutorial.

--

--

Andreas Pogiatzis
Andreas Pogiatzis

Written by Andreas Pogiatzis

☰ PhD Candidate @ UoG ● Combining Cyber Security with Data Science ● Writing to Understand

Responses (6)