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/).

Enhanced REST framework user interface OPTIONS
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].

  1. http://marcgibbons.github.io/django-rest-swagger/    

  2. http://drfdocs.com/