Django REST framework introduction Part 2: Auth, Token and Permissions

A web developer worthy of the name must be able to create a REST API.

In part 1 we help you understand everything there is to know to build your first API using the Python language and the Django REST Framework.

In part 2 we will secure our API. After all, not every action (ex. DELETE) can be authorized. We will add REST Framework authentication, token and permissions to our API.

To not miss anything, click Follow and/or follow me on Twitter: https://twitter.com/EricTheCoder_

What is the next step ?

The next step is to secure our API.

Until now, anyone who knows our URL can send a read, create, update, and delete request to our API. Not good!

So the goal being to allow create, update and the deletion of
a post only for authenticated user and only allow those to update and delete their own posts

In order to determine what the identity of the request user is, we will also have to add an authentication (login) to our API

How authentication work in REST Framework?

There are several ways to manage authentication with REST-Framework.

In this article, I will show you one that is often used: Token Authentication

What is Token Authentication?

Token authentication allows users to verify their identity, and in return receive a unique access token. During the life of the token, users then access the API that the token has been issued for, rather than having to re-enter credentials each time they go back to the same API.

Auth tokens work like a stamped ticket. The user retains access as long as the token remains valid.

Token-based authentication is different from traditional password-based or server-based authentication techniques. Tokens offer a second layer of security, and administrators have detailed control over each action and transaction.

In short, it means that when you will make a login request (ex: api/login), our API will give you back a Token.

It will then be possible to use this Token to have access to certain action protected by the API (ex: DELETE api/posts/1)

How to use the Token?

To use the Token, In the request header, you need to add an 'Authorization' key:

'Authorization' : 'Token 622af924b5e828dd35dd6'

Here a JavaScript fetch() example to delete post no 3

const token = 'YOUR_TOKEN_HERE';
const response = await fetch('api/posts/3', {
    method: 'DELETE',
    headers: {
      Authorization: `Token ${token}`
    },
  });

Implement a Token base auth in REST-Framework

To implement this pattern in our API we need 2 things:

First, we need to create a login API endpoint (ex: api/login). That login will authenticate users and return the Token.

Second, we need to modify our update and delete API endpoint to allow only an authenticated user to perform those actions on their own posts. This will be implemented with the Authorization and Permissions class in REST-Framework

Create a Login endpoint

That part will be pretty easy because REST-Framework already have a login view pre-created for us.

In posts application folder, open views.py and add this code

from rest_framework.authtoken.views import ObtainAuthToken 
from rest_framework.settings import api_settings

class UserLoginApiView(ObtainAuthToken):
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES

Noted, for simplicity, I only show code related to the Login view.

This UserLoginAPIView inherit from ObtainAuthToken that contain all the code to create the login view and all the methods to authenticate a user and return a Token.

Next we need to create a URL that point to this login view

Open posts application urls.py and add this URL path:

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PostViewSet, UserLoginApiView

router = DefaultRouter()
router.register('posts', PostViewSet)

urlpatterns = [
    path('', include(router.urls)),
    path('login/', UserLoginApiView.as_view()),
]

Finally, in order to use the REST-Framework auth libraries, we need to add a reference to 'rest_framework.authtoken' to our project settings.py files.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'posts',
    'rest_framework.authtoken',
]

And re-run the 'migrate' terminal command

python manage.py migrate

That action will create the database table to support authtoken functionalities

If everything code right, you can start your server and visit api/login

python manage.py runserver

You can also add users with the admin panel if needed.

Once logged in, the API will return you a Token

Add Authorization and Permissions to Update and Delete API endpoint

Here the objective is to limit access to our API. In particular, the Update and Delete actions. So only users who are authenticated will be able to perform these actions.

But we also need to check if the authenticated user is the author of the post. Only author of their own posts will be able to do Update and Delete request.

Permissions class

REST-Framework has a class to manage permissions. We can create one and associated the permissions class to our API view

Create a new file under posts application folder and name it permissions.py. Inside that file add the following code:

from rest_framework import permissions

class UpdateOwnProfile(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True

        return obj.user.id == request.user.id

If the action to be executed is considered "safe" (request.method in permissions.SAFE_METHODS), it can be executed (return True)

If on the other hand, the action is not safe (Create, Update and Delete action) then it checks that the authenticated user and are the author of the post are the same (obj.user.id == request.user.id)

Bind permissions to our API

Now that our Permission object is created, we can bind it to our API.

In posts application folder, open views.py and add this code:

from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticatedOrReadOnly

from .models import Post
from .serializers import PostSerializer
from .permissions import UpdateOwnProfile

class PostViewSet(viewsets.ModelViewSet):
    serializer_class = PostSerializer
    authentication_classes = (TokenAuthentication,)
    permission_classes = (
        UpdateOwnProfile,
        IsAuthenticatedOrReadOnly,
    )
    queryset = Post.objects.all()

We add 2 thing here. First we set the authetification_classes to TokenAuthetification and then we set our permissions_classes to our custom permission class. The isAuthenticatedOrReadOnly will allow authenticated users to perform any request. Requests for unauthorised users will only be permitted if the request method is one of the "safe" methods like GET (read post).

Test your API

Our API is now secure, it's time to give it a try.

This time we cannot test our API in the browser because for auth we need a way to send the Token with the request.

We will use an API testing application. Postman is one of the most use app but today I will use a VSCode extension call Thunder Client. It's a light version of Postman easy to install and use

Install Thunder Client

From VSCode, go to extension, find and then install the Thunder Client application.

Once install, open the app and click on 'New Request' button and type the API URL you want to test (ex: api/posts) then click 'Send' and the response would appear in the right portion of the screen

Login and Token example

To login you need to do a POST request with credentials to api/login. First, click the 'New Request' button, choose POST from the request type dropdown, type the request URL (ex: api/login/), click the Body tab and add the credential in JSON format:

{
    "username": "ericthecoder",
    "password": "yourpassword"
}

Click 'Send' and the response section on the right should display the received Token
Screen Shot 2021-09-03 at 1.43.40 PM.png

Create a new Post

Now that we have our Token, we can do action that required permissions. So let's create a new Post.

Click 'New Request', choose POST from dropdown and type the create post URL (ex: api/posts/). Click the Body tab and enter the create post JSON Content:

{
    "title": "My First Blog Title",
    "body": "My first Blog body Content",
    "user": 1
}

Click send, and the API will return a 401 Unauthorized message. That's normal, since we did not send the Token with the request.
Screen Shot 2021-09-03 at 4.45.20 PM.png

To do so, click on Headers tab and add the Authorization key and Token
Screen Shot 2021-09-03 at 1.54.49 PM.png

Click 'Send' and now the Post should be created with success
Screen Shot 2021-09-03 at 1.54.36 PM.png

Test all API endpoints

With your Token now in hand, you can now test all our API endpoint:

POST api/login/ (login with credentials)

GET api/posts (Read all post)
POST api/posts/ (Create a new post)

GET api/posts/1/ (Read a specific Post)
PUT api/posts/1/ (Update a specific Post)
PATCH api/posts/1/ (Partial Update a specific Post)
DELETE api/posts/1/ (Delete a specific Post)

End of part 2

That's all for today. You now have a secure API, in the next part, we will discuss search, pagination and image upload.

To not miss anything, click Follow and/or also follow me on Twitter: https://twitter.com/EricTheCoder_

22