22
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_
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
There are several ways to manage authentication with REST-Framework.
In this article, I will show you one that is often used: 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)
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}`
},
});
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
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
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.
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)
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).
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
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
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"
}
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.
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)
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