JWT Auth with DjangoREST API

Bonfire Algorithm
The Startup
Published in
8 min readNov 3, 2019

--

Token based authentication allows the server and the frontend (whether that be web, native mobile or something else) to be decoupled and reside on different domains. JSON Web Tokens (JWT) is a popular implementation of token based authentication, and in this article we’ll use it to authenticate users in an API for notes built with Django REST Framework.

We’ll set up user registration and authentication, and we will define the notes model. We will also ensure that the current logged in user is set as the owner of a note when it is created, and that a user can perform read and write operations only to his own notes.

The full source code for the final project is here: https://github.com/bonfirealgorithm/notes-api

1. Create Django Project

$ mkdir notes && cd notes

Create a virtual environment and activate it.

$ python3 -m venv venv
$ source venv/bin/activate

Install Django, DjangoREST and django-cors-headers:

$ pip install django django-rest-framework django-cors-headers

We install basic Django, as well as Django REST Framework, which will assist us in implementing the API. We also install django-cors-headers, which makes it possible to access our endpoints from browsers on other domains than the Django server.

Next we create a new Django project:

$ django-admin startproject project .

The dot at the end of the command will create the project inside the project we’re in, the notes directory.

Next we create a notes app for our project and run migrations:

$ python manage.py startapp notes
$ python manage.py migrate

We’ll add the installed apps and REST Framework settings in project/settings.py:

project/settings.py

To check that everything’s working, let’s run the server:

$ python manage.py runserver

Visit http://localhost:8000

You should see the Django welcome page.

But we still have no API and no data to serve in the API. Let’s make a notes model next.

2. Create a Notes Model and a Superuser

In notes/models.py add the following:

We create a Note model with five attributes. The owner is a User object, which we import from Django’s auth module. Later, when we have added authentication, we will ensure that the owner is set to the currently logged in user. We also have title and content for the note, a shorter char field and a longer text field, respectively. Lastly we add a created and updated field.

Also register the model with the admin site so that it will appear there. notes/admin.py:

Since we’ve created a new model we need to create and run migrations to sync it with the database:

$ python manage.py makemigrations
$ python manage.py migrate

Let’s also create a superuser so we can inspect and create notes objects in the admin panel. (You can just select the default username and leave email blank when prompted.)

$ python manage.py createsuperuser

Let’s start the server again.

$ python manage.py runserver

Then visit http://localhost:8000/admin/ and log in with your superuser’s credentials.

You should see something like this:

Add a couple of notes so we have some data to work with.

3. Serve the Notes data as an API

We now need to add some urls so we can access the notes API.

Change project/urls.py to this:

Next create the file notes/urls.py and add the following:

We’re using rest_framework’s SimpleRouter to automatically create the routes for us. Also notice that we import NoteViewSet, which we haven’t created yet. We’ll do that soon, but first we need to create a serializer for our model.

Serializers come with rest_framework and provide a way to translate our model’s data back and forth to a format that is suitable for our API, in our case JSON. Serializers can also perform validations as well as let us specify which fields to include in the data we exchange with the rest of the world.

Create the file notes/serializers.py:

Next we’ll create the view in notes/views.py:

Ensure that your are logged in with your superuser, and go to http://localhost:8000/api/notes/

You should now be able to see the notes you have created, as well as create new notes.

4. Authentication and Permissions

For us to explore authentication and permission, create a new user in the admin panel. (All you need to add is username and password.)

Then add the following line to the urlpatterns list in project/urls.py

path('auth/', include('rest_framework.urls')),

This ensures that we have a path for logging in with django_rest. You’ll now see that there’s a down arrow by the user name in the upper right corner:

If you click on it you can log out and switch between users.

One obvious problem now is that every user can see every other user’s notes. Even worse, users who are not logged in can see and create notes. (You can verify this by logging on and then going to http://localhost:8000/api/notes/)

Let’s fix that.

The reason even users who are not logged in have access to notes, is that we have configured rest_framework with AllowAny. Go into your project/settings.py file and add the following code:

Then try to access http://localhost:8000/api/notes/

As long as you are logged out, you should no longer see any notes, but instead receive the following message: “detail”: “Authentication credentials were not provided.”

Log in again, and you will see the notes. How can we fix the problem with one logged in user being able to see every other user’s notes?

Make the following changes in notes/views.py:

We remove the queryset property from the class and instead override the get_queryset method, where we filter the notes objects to ensure we only return those who belong to the current user.

Furtermore we add an IsOwner permission check. This ensures that a user can only modify (update/delete) his own objects.

Lastly we override the perform_create method so that when a new note object is created the owner is always set to the current user.

If you visit http://localhost:8000/api/notes/ you should only see the notes belonging to the current logged in user. If you try to make a new note and set the owner to be another user than the one you are logged in as, the note will be created, but the user will be the current logged in user regardless.

With these changes in place we can remove the owner field from the serializer, since any note a user will deal with will always be the user’s own. Make the following change to notes/serializers.py:

We have now implemented authentication and permissions, but django_rest is still using session_authentication.

What we want is for any client, web or otherwise, to be able to register a user, login and logout and be authenticated from anywhere. To do that we will use JWT.

5. Adding JSON Web Token Authentication

To implement token authentication with JWT, we will use a library, Simple JWT:

$ pip install djangorestframework_simplejwt

Then we need to add it to our list of authentication classes in our project/settings.py file:

And add two new endpoints to our project/urls.py file:

These new endpoints provide what we need for user log in. The route for login would be /api/token/. The /api/refresh/ route is used to get a new token before the old expires.

And we actually don’t need an endpoint for logging out, since the server doesn’t maintain any state. To log out we can simply delete the token on the client. The token will expire “on it’s own” (the time can be set using Simple JWT settings).

But what we currently miss is a way to register a user and get a JWT token back.

User Sign Up

We need a new endpoint where users can sign up. This doesn’t belong in the Notes app, since this is another domain, authentication. So we’ll start by creating a new app, jwtauth:

$ python manage.py startapp jwtauth

Add app in project/settings.py:

Next we’ll need a serializer for the User object. Add jwtauth/serializers.py:

We’ll be using Django’s built in User model, which we get by calling the get_user_model() function. It’s good practice to do it this way instead of importing the User directly, since it will ensure that we get the currently active User model even if we have customized it.

We also override the create() method and check that the confirmation password is identical to the password, and that no other user has the same email address.

Next we’ll add a view in jwtauth/views.py:

This is the first time we use a function based view and not a class based view. We choose a function based view here since it only responds to the POST http verb, and we use decorators to ensure this, as well as make and exception from the permissions defined in the settings.py file to allow anyone access to just this endpoint.

We do a check to see if the serializer has validated the data we got, and if not return it’s error object. If everything is fine we save the serializer, which returns the newly created user object. We can then obtain a JWT token for this user and return it.

We need to create a urls file add the view to it, jwtauth/urls.py:

And lastly include the jwtauth urls in project/urls.py:

And that’s it. We should now have a new endpoint at http://localhost:8000/api/jwtaut/register/

We can test it in Postman:

If the validations pass it should return an object with refresh and access tokens.

6. Adding Swagger Docs

The last thing we’ll do is add Swagger docs so consumers of the API can see what endpoints are available.

$ pip install django-rest-swagger

Add it to your INSTALLED_APPS list in project/settings.py. Also include the new DEFAULT_SCHEMA_CLASS in REST_FRAMEWORK settings:

And include it in your project/urls.py file. To finish up we’ll also do a little bit of refactoring. Refresh and token are separate endpoints now. Since they have to do with authentication it would be better if they were in the jwtauth app. Let’s move them there, as well as add a url for the docs.

Then put the token and refresh enpoints in jwtauth/urls.py:

Go to http://localhost:8000/api/docs/ to see the full list of APIs. You should also see that the token endpoints now belong to the /jwtauth grouping.

And that’s it! You now have a fully functionall notes API that can be consumed by a client from anywhere.

--

--