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
django-admin startapp accounts
Do not forget to use virtualenv.
Your folder structure should look like this
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ └── views.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
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
sample_rest_api/settings.py and also
INSTALLED_APPS = [
... 'rest_framework', 'accounts']
Before anything else, we create a urls.py file in the accounts directory that looks like this:
The we include it in the main
Let’s now go to our
accounts/models.py to create a custom user model with only the email and password field.
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.
serializer.py will look like this:
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
.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
@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.
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
We can now write our login view.
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
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.
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
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.
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:
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.