Django REST framework security
Although the Django REST framework represents a great time saver to create REST services that allow access to an application's data, you need to be careful you don't inadvertently allow access to data or functionalities you didn't mean to. Using the wrong class or configuration parameter with the Django REST framework can leave your application open to a security risk, even though the Django REST framework is fundamentally secure.
Set up REST framework services permissions
By default, all REST framework services are open to anyone, so long as they know or discover a REST service endpoint (i.e. the url). While this default behavior is convenient, it can also represent a grave security threat, particularly if you create REST framework services with sensitive operations (e.g. Update or Delete actions) or view sets -- such as the one in listing 12-9 -- which automatically create end points that support sensitive operations.
The default REST framework
permission can be configured through the
REST_FRAMEWORK
variable in setting.py
file, via the DEFAULT_PERMISSION_CLASSES
option. Out
of the box, the REST framework sets this option to use the
rest_framework.permissions.AllowAny
class, as
illustrated in the following snippet:
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.AllowAny', ) }
This means that unless you define
a DEFAULT_PERMISSION_CLASSES
option in your Django
project's settings.py
file, all REST framework
services are open to the public. To lock down access to REST
services to the public, you can change the REST framework's default
permission strategy.
Listing 12-11 illustrates the
DEFAULT_PERMISSION_CLASSES
option set to the
rest_framework.permissions.IsAuthenticated
class,
which enforces that only users logged-in via Django's built-in user
system -- described in Chapter 10 -- be allowed access to REST
framework services.
Listing 12-11 Django REST framework set to restrict all services to authenticated users.
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ) }
In addition to the
rest_framework.permissions.IsAuthenticated
class, the
REST framework also supports the classes in table 12-1 to be part of
the DEFAULT_PERMISSION_CLASSES
option.
Table 12-1 Django REST framework permission classes
REST framework permission class | Description |
---|---|
rest_framework.permissions.AllowAny | (Default) Allows access to anyone |
rest_framework.permissions.IsAuthenticated | Allows access to logged-in users via Django's built-in user system. |
rest_framework.permissions.IsAdminUser | Allows access to Django admin users, based on Django's built-in user system. |
rest_framework.permissions.IsAuthenticatedOrReadOnly | Allows read access to anyone (logged in or not), but requires logging in to perform non-read operations. |
rest_framework.permissions.DjangoModelPermissions | Allows access to logged-in users, but also requires that said users have the necessary add/change/delete model permissions on which the REST service operates with. |
rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly | Just like the DjangoModelPermissions class, but allows read access to anyone (logged in or not). |
rest_framework.permissions.DjangoObjectPermissions | Similar to the DjangoModelPermissions class, except it works per-object permissions on models on which the REST service operates with. |
As you can see in table 12-1, the
REST framework provides various classes to set the the default
access permission for all REST services in a project. For example,
if you're fine allowing public read access to a project's REST
services but want to restrict more sensitive REST services
operations, the IsAuthenticatedOrReadOnly
and
DjangoModelPermissionsOrAnonReadOnly
classes from
table 12-1 are good alternatives for the
DEFAULT_PERMISSION_CLASSES
option.
Still, by relying on the
DEFAULT_PERMISSION_CLASSES
option you give every REST
service in a project the same access permission. What if you want
to provide a more flexible or strict permission strategy for one or
two services ? The REST framework also supports specifying more
granular permissions on individual REST services, using the same
classes in table 12-1.
Listing 12-12 illustrates a
modified version of the REST service from listing 12-4 that uses
@permission_classes
decorator to specify a different
permission strategy than the global
DEFAULT_PERMISSION_CLASSES
option.
Listing 12-12. Django view method decorated with Django REST framework and @permission_classes decorator
from coffeehouse.stores.models import Store from coffeehouse.stores.serializers import StoreSerializer from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @api_view(['GET','POST','DELETE']) @permission_classes((IsAuthenticated, )) def rest_store(request): if request.method == 'GET': stores = Store.objects.all() serializer = StoreSerializer(stores, many=True) return Response(serializer.data)
As you can see in listing 12-12,
the standard view method in addition to being decorated with
@api_view
is also decorated with
@permission_classes
. In this case, the
@permission_classes
decorator is set with the
IsAuthenticated
class values, ensuring this REST
service permission strategy take precedence on the service over the
default service permission in
DEFAULT_PERMISSION_CLASSES
option.
Listing 12-13 illustrates a
modified version of the REST service from listing 12-9 that uses
permission_classes field to specify a different permission strategy
than the global DEFAULT_PERMISSION_CLASSES
option.
Listing 12-13 Django viewset class in Django REST framework and permission_classes field
from coffeehouse.stores.models import Store from coffeehouse.stores.serializers import StoreSerializer from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets class StoreViewSet(viewsets.ModelViewSet): permission_classes = (IsAuthenticated,) queryset = Store.objects.all() serializer_class = StoreSerializer
As you can see in listing 12-13,
the REST framework view set method makes use of the
permission_classes
field. In this case, the
permission_classes
field is set with the
IsAuthenticated
class values, ensuring this REST
service permission strategy take precedence on the service over the
default service permission in
DEFAULT_PERMISSION_CLASSES
option.
Set up REST framework login page
By default, if a user attempts to access a REST framework via a browser and doesn't have the necessary permissions, a user is presented with a warning page like the one in figure 12-5.
Figure 12-5. Django REST framework access denied page
Although figure 12-5 represents a standard access denied page, it has one glaring omission: it's a dead-end with no link to let users log in. If a user reaches the page in figure 12-5, he needs to manually go to a Django log in page in either the application or the Django admin, to provide his credentials and then go back to the REST service to access it.
This last workflow creates an unnecessary burden on users, which is why the REST framework provides an easy way to integrate a log in page -- including a log in/log out link on all its pages -- that's tied directly to the same authentication back-end of the Django admin.
Listing 12-14 illustrates a Django project's main urls.py which declares the REST fraemwork's urls to automatically activate its built-in log in page and links.
Listing 12-14 Django REST framework url declaration to enable log in
from django.conf.urls import include, url urlpatterns = [ url(r'^rest-auth/', include('rest_framework.urls',namespace='rest_framework')), ]
In listing 12-14 you can see
rest_framework.urls
is configured with Django's
standard include()
statement and
namespace
argument, in addition to being configured on
the rest-auth/
url, the last of which you can change
to any url pattern of your choosing.
With this addition to a Django
project's main urls.py
file, all REST framework pages
are generated with a log in link in the top right corner, which
takes users to a log in page accessibly under the login path of the
url configuration (e.g. if url(r'^rest-auth/')
, the
log in page is available at /rest-auth/login/
).
Although the built-in user interface (UI) provided by the REST framework -- presented in figure 12-3, figure 12-4 and figure 12-5 -- offers a better alternative than presenting raw data REST services output -- like figure 12-1 and figure 12-2 -- the REST framework UI is still rather rudimentary when you compare it to more modern UI web layouts.There are various alternatives to the REST framework UI, which are specifically designed to work with the REST framework. Some of the more mature projects includeDjango REST Swagger[8] and DRF Docs[9].