Django Rest Framework, AngularJS and permissions

by Sebastien Mirolo on Mon, 25 Apr 2016

We are building an AngularJS application that will talk to a backend API written with Django Rest Framework (DRF for short). Let's see how we do that nicely and securely.

Problems to solve

Running an application on the Internet requires to authenticate clients and servers which each other. If your phone number is listed in the yellow pages, anyone can call you. So both, client and server must be mutually distrustful until they can verify each other's identity.

After an HTTPS connection is successfully created between the client and server and credentials have been exchanged to authenticate a user, the front-end AngularJS application is mostly concerned with two problems:

  • How to show a different interfaces based on a user permissions?
  • How to authenticate the AngularJS application with the API?
The back-end DRF is concerned with:
  • request authentication
  • and checking permissions

From a maintenance point of view, we are also concerned with:

  • How to pass the API end-points to the AngularJS code?

Scaffolding

The main application page is a Django template. This means we can do two things:

  1. Pass API end-points to the AngularJS code
  2. Customize the interface based on a user permissions

First, passing the API end-points requires to define a code convention to write our AngularJS code. We always define a settings constant parameter that gets passed to all controllers. We then write our Django template to configure the settings parameter at runtime. The relevant code looks like:

Terminal
$ cat app/static/my-angular-app.js
...
angular.module("MyApp", ["ngRoute", "MyControllers"]);
angular.module("MyControllers", []).controller("MyMainCtrl",
    ["$scope", "$http", "settings", function($scope, $http, settings) {

    $scope.items = [];

    $http.get(settings.urls.api_end_point).then(function success(resp) {
        $scope.items = resp.data;
    });

}]);

$ cat app/templates/index.html
...
<script type="text/javascript" charset="utf-8" src="{% static 'my-angular-app.js' %}"></script>
<script type="text/javascript" charset="utf-8">
/* Implementation Note: Not including a list of dependent modules
   (2nd parameter to `module`) "re-opens" the module for additional
   configuration. */
var reopenMyApp = angular.module('MyApp');
reopenMyApp.constant('settings', {
    urls: { api_end_point: "{% url 'api_end_point' %}" }
});
</script>

Since anyone can call our API end-points (i.e. they know our number), we must implement the permission checks server-side. None-the-less it would be a very poor interface to show buttons and links to a user that will always result in 403 - Permission denied. As a side note: Why your user interface is awkward. You are ignoring the UI stack is a great read on the subject.

The customization of the interface can be done in two different ways. If we have an AngularJS-light application, we generate different HTML server-side through the Django template engine.

Terminal
$ cat app/templates/index.html
...
{% if request.user|has_admin_permission %}
  <button ng-click="doSomethingAdminy()">Click if you are an Admin!</button>
{% endif %}

If we have an AngularJS-heavy application, we pass the user permissions through the *settings* parameter and customize the interface client-side.

Terminal
$ cat app/templates/index.html
...
  <button ng-show="user.is_admin">Click if you are an Admin!</button>
...
<script type="text/javascript" charset="utf-8">
...
reopenMyApp.constant('settings', {
    urls: { api_end_point: "{% url 'api_end_point' %}" },
    user: { is_admin: {{request.user|has_admin_permission}} }
});
</script>

API POST/PUT/PATCH requests

Not all our API calls will be GET requests. We also want to modify state on the server at some point. Django will check the CSRF token in those cases. We thus need to communicate the CSRF token to AngularJS and tell AngularJS to pass it back to the server on POST/PUT/PATCH requests.

Terminal
$ cat app/templates/index.html
...
<script type="text/javascript" charset="utf-8">
var reopenMyApp = angular.module('MyApp');
reopenMyApp.config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.headers.common['X-CSRFToken'] = '{{csrf_token}}';
}]);

Mixing Django template syntax and AngularJS syntax

One last scaffolding step. Both Django and AngularJS, by default, uses {{...}} to mark templated elements. Lucky enough AngularJS permits to redefine the template markers. We redefine them to be [[...]] for our AngularJS templates code written in Django templates.

Terminal
$ cat app/templates/index.html
...
<script type="text/javascript" charset="utf-8">
var reopenMyApp = angular.module('MyApp');
reopenMyApp.config(['$interpolateProvider', '$httpProvider',
    function($interpolateProvider, $httpProvider) {
    $httpProvider.defaults.headers.common['X-CSRFToken'] = '{{csrf_token}}';
    $interpolateProvider.startSymbol('[[');
    $interpolateProvider.endSymbol(']]');
}]);

Client/Server authentication

Django will associate a User to a request through a session and auth middleware classes.

Terminal
$ cat app/settings.py
...
MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
)

For some reasons I am yet to fully grasp or agree with, DRF implements its own Request class and does authentication, not through middlewares but in the View class directly (i.e. after dispatch is called). Authentication in DRF is very flexible. Yet for most intended purposes, security audits being first, it is a lot better to rely on a default settings and not override authentication on a view-per-view basis.

Since we are building a client/server application and we have already authenticated the User through a session cookie, we will set Django Rest Framework default to session-based authentication.

Terminal
$ cat app/settings.py
...
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
    )
}

Checking permissions

With scaffolding and session authentication in place, we can present a user-tailored interface and our AngularJS application can now reliably talk to the server. It is time to restrict what can be done server-side based on the request.user.

Here we have to understand a bit of how the Django and Django Restframework pipelines work.

In a typical Django setup, there is multiple places we can add permissions checks:

  • Globally as a middleware
  • as a decorator on a View method
  • or as code inside the View method implementation.

DRF extends the request pipeline with 4 steps that usually happen before your overridden view method is called (see View.dispatch in rest_framrwork/views.py).

  • perform_authentication(request)
  • check_permissions(request)
  • check_throttles(request)
  • check GET, POST, etc. method is allowed

So with DRF, you also have the additional opportunity to use the framework to define a custom permissions check step. That's nice but we will keep with our rule:

Make it straightforward to do security audits by reading urls.py

We will thus rely on the django-urldecorators project and add our permissions checks as decorators in the top-level urls.py file. This works with both Django Views and DRF Views.

Terminal
$ cat app/urls.py

from urldecorators import url

urlpatterns = [
...
    url(r'^api/(?P[a-z])/',
        decorators=['django.contrib.auth.decorators.login_required'],
        name='api_end_point')
]

Et Voila! To understand which kind of permissions decorators are necessary to run a Software-as-a-Service business, please read about djaodjin-saas flexible security framework.

More to read

If you are looking for more Django/Javascript posts, Continuous Integration for a Javascript-heavy Django Site and Serving static assets in a micro-services environment are worth reading next.

More technical posts are also available on the DjaoDjin blog, as well as business lessons we learned running a SaaS application hosting platform.

by Sebastien Mirolo on Mon, 25 Apr 2016


Receive news about DjaoDjin in your inbox.

Bring fully-featured SaaS products to production faster.

Follow us on