Django Rest Framework, AngularJS and permissions
by Sebastien Mirolo on Mon, 25 Apr 2016We 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?
- 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:
- Pass API end-points to the AngularJS code
- 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:
$ 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.
$ 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.
$ 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.
$ 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.
$ 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.
$ 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.
$ 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:
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.
$ 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.