Building APIs is not a straightforward job. One has to not only write the business logic but also have a permission layer to protect an unauthenticated user from accessing APIs that are public.
Restricting unauthenticated access alone is not enough though, there are cases where a user need not be authenticated for read permissions but should not be given write permissions.
For instance, if I am building a website for cake recipes, I would want anybody to view all the recipes but only an authenticated user should be able to edit the recipes.
What we essentially need to accomplish is role based access. This is where Django Rest Framework shines.
Handling all of these cases should be a separate module that you can just plug in at the end once your business logic is complete.
Guess what? It is actually that simple if you use Django Rest Framework. So before we go any further please install DRF in your django app.
As mentioned earlier, there are two distinct concepts that need to be applied here.
The first one being identifying a user that is making a request based on various authentication backends that can be configured in settings.py of your django project.
This can be done by using DEFAULT_AUTHENTICATION_CLASSES. You can configure this using just a couple of lines of code.
This setting will ensure that if a API request contains a header called Authorization with a value that is equal to Token <auth_token>, request.user will automatically be set to the current user.
You could also use SessionAuthentication or any of the other authentication backends that are available that can be found here.
I usually prefer TokenAuthentication because the auth token is fixed and as long as it is present in the headers, one does not have to worry about saving anything in cookies and deal with making API calls to a domain that is different from the one where your frontend is hosted.
To generate an auth token for a Django user, simply include the below apps in your INSTALLED_APPS section and use this code to generate a token.
If you prefer not to apply the TokenAuthentication backend globally to all your APIs and only want to apply it to a few APIs, you could define a base view where the authentication class can be set explicitly and inherit this view wherever needed
Now that we have taken care of authenticating a user, let us look at how we can perform role based access in the quickest possible way.
This can be achieved by using the DEFAULT_PERMISSION_CLASSES setting.
This setting will ensure that any user who is authenticated has permission to use all your APIs. But this obviously won’t work in most cases since we discussed earlier that we would require role based access.
You can achieve this by explicitly defining permission classes in your base view or individual views just like the authentication classes that were defined above.
DRF provides a bunch of permission classes out of the box which can be found here. For the sake of an example let’s use the IsAuthenticatedOrReadOnly permission.
As the name suggests this permission allows an API to be accessed only if you are an authenticated user unless the API you are trying to access is read only in which case anybody can access it.
This solves the problem of the cake recipes example given earlier and can be translated to code in the following way.
If you try to access this API via the GET, HEAD, OPTIONS methods, you will get a response, but if you try to make a POST, PUT, PATCH call you will see the following response.
In most cases, role based access would involve some specific business logic. For instance, you might have roles such as user, editor and admin.
To restrict access based on custom parameters like these. You can write a custom permission class by inheriting the BasePermission class and overriding the has_permission method.
You could then chain these permissions together in views to determine which users will have access to which views.
In conclusion, the quickest way to authenticate users and give them role based access is to use the TokenAuthentication class globally and then custom permission classes for your individual views.
Alternatively you could write a base view and give it some authentication classes as well as permission classes that cover most of your cases and inherit all your views from this base view.
I prefer the latter method as it gives me more control and I can choose not to inherit this base view in some cases where I want to set custom authentication and permissions.