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.
$ python -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:
To check that everything’s working, let’s run the server:
$ python manage.py runserver
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 three 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. The two other fields are the title and the content of the note, a shorter char field and a longer text field, respectively.
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
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 make the following change:
Then try to access http://localhost:8000/api/notes/
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 to 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.