Building REST API with Django (Part 1)

Favour Okedele
Mar 10, 2019 · 4 min read

Authentication with JWT

In this article, we will be implementing a custom user in Django (Note: We are not going to use the Django user model to avoid some unnecessary complexities). We are also going to return JWT tokens and use a middleware to validate them and also protect some routes.

You must have some experience with django before reading this article. You can get started here.

First of all, let’s setup the project

virtualenv venv && source venv/bin/activate
django-admin startproject sample_rest_api
cd sample_rest_api
django-admin startapp accounts

Do not forget to use virtualenv.

Your folder structure should look like this

├── accounts
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
├── sample_rest_api
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── venv

We are going to be using some modules from the Django Rest Framework so we should install it

pip install djangorestframework

Add the name of the app you created to INSTALLED_APPS in sample_rest_api/settings.py and also rest_framework

INSTALLED_APPS = [
...

Before anything else, we create a urls.py file in the accounts directory that looks like this:

accounts/urls.py

The we include it in the main sample_rest_api/urls.py file:

sample_rest_api/urls.py

Let’s now go to our accounts/models.py to create a custom user model with only the email and password field.

accounts/models.py

Run the next commands and fill in the required values to migrate and also create a super user.

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

Next, we create our serializers.py file. It works mainly like Django forms and you can read more on it in the Django Rest Framework documentation.

For this part, we need to install another module called bcrypt. Use the command

pip install bcrypt

and you can also check the documentation here for further information on it.

Our serializer.py will look like this:

accounts/serializers.py

The serializers.ModelSerializer acts the same way as forms.ModelForm in Django. The create function will be executed when the the data is to be saved. It takes the validated data, gets the password and hashes it with the bcrpyt library.

The reason from the .encode('utf-8') and .decode('utf-8') is because the bcrypt.hashpw function only accept bytes and the encode function converts strings to bytes, the decode function does the opposite.

For this part, we need to install PyJWT.

$ pip install pyjwt

Then we go to our views.py file:

accounts/views.py

The @csrf_exempt decorator is used when using POST request. Normally when submitting forms in normal Django, we have to add a csrf_token for the form to get to the view or it will be rejected by the csrf middleware in Django. This decorator exempts from having to send the token.

The JSONParser().parse(request) is used to parse the request and return the body of POST request which will then be saved by the serializer if it is valid else, it will be rejected and an error will be sent back.

We get the email from the saved data and use it to create a jwt token using the library installed previously and send a response with the token back to the client.

We add this route to our urls.py file

accounts/urls.py

We can now write our login view.

accounts/views.py

We get the email and password from the request body and we try to get a user with a corresponding email. If the user doesn’t exist, an error message is sent back to the user.

If the user exists, we get the hashed password and use bcrypt to compare them, if they match, a token is created and sent back to the user.

The path is also added to accounts/urls.py

accounts/urls.py

Next we are going to create a middleware to check from JWT tokens in the request header before we permit access to paths on the server except the admin, signup and login path.

The process_view function is executed before calling the view and if it returns None, the view is executed. We use the reverse library to check with the request path for the signup, login and admin routes, if they match, we allow the request to continue, else we get the Authorization header represented as HTTP_AUTHORIZATION.

The header should be sent like this: Authorization: Bearer <token> the value of the token is gotten from the header and decoded, if the token is expired or invalid, an appropriate response is sent back to the client else, we add a key to the request.META dictionary called AUTH_USER which is the payload of the JWT. The payload can then be accessed in the views function.

Next, we create a views function that can only be accessed by a valid JWT.

accounts/views.py

In this view function, we print the AUTH_USER field that was added by the middleware token just to confirm it is working fine.

The path is also added to the accounts/urls.py file. The complete accounts/urls.py file is:

accounts/urls.py

That is all. If a request is to be sent to the path /api/accounts/secret, it must have a valid JWT in its header else the request will not be completed.

In the next article, we are going to be talking about image/file upload through api requests.

Hacktive Devs

Engineering, design, and technology articles for designers…