I’ve wanted to get started building applications with Ember for a while, but I never invested the time to figure out how to integrate it with Django. I’ve spent the last few days doing just that, however, and it’s been a nightmare of outdated libraries and vague documentation. The main obstacle was getting authentication working. At one point I ended up on page 7 of Google, so that will give you an idea of how bad it was. I’m writing this post so you don’t have to go through the same pain.

Here is what we are going to build:

An Ember todo list CRUD app

Using a JSON API-compliant backend built with Django Rest Framework

Secured using token authentication

With the ability to login and register new users

Here are some screenshots of the (unstyled) finished application:

This tutorial is for Ember 2.4 and Django 1.9. I recommend that you go through the basic Ember tutorial first to orientate yourself, although I will try to explain everything as I go along.

Setting up the project

We’re going to put the Ember project and the Django project side by side in the same directory. We are not going to embed the Ember project within Django’s static files, as some people do. There is really no reason to do that because the whole point of front-end Javascript frameworks is that they are backend-agnostic – they should work equally well with any backend that implements the necessary API calls. It is not even obvious that we would put the Ember client and the Django API on the same server, so it is best to keep them separate.

Create a directory to hold everything and cd into it:

$ mkdir todo-djember<br /><br />

$ cd todo-djember 1 2 $ mkdir todo – djember $ cd todo – djember

Use the ember new command to generate an Ember application called todo-ember in that directory:

$ ember new todo-ember 1 $ ember new todo – ember

cd into the directory and install some Ember libraries that we are going to need:

$ cd todo-ember<br /><br />

$ ember install ember-simple-auth<br /><br />

$ ember install ember-cli-scaffold 1 2 3 $ cd todo – ember $ ember install ember – simple – auth $ ember install ember – cli – scaffold

Now we will generate the Django project. We will also start a virtualenv for it. From the root directory – the one where we ran ember new – run the following commands:

$ virtualenv -p /usr/bin/python2.7 venv<br /><br />

$ source venv/bin/activate</p><br />

<p>$ pip install django<br /><br />

$ django-admin.py startproject todo_django 1 2 3 4 5 $ virtualenv – p / usr / bin / python2 . 7 venv $ source venv / bin / activate $ pip install django $ django – admin . py startproject todo_django

And we will generate an app inside our project to hold our todo list implementation:

$ cd todo_django<br /><br />

$ python manage.py startapp todo 1 2 $ cd todo _ django $ python manage . py startapp todo

We’re going to need some Django packages to build an API that plays nice with Ember. Install them using pip :

$ pip install djangorestframework<br /><br />

$ pip install django-cors-headers<br /><br />

$ pip install djangorestframework-jsonapi==2.0.0-beta.2 1 2 3 $ pip install djangorestframework $ pip install django – cors – headers $ pip install djangorestframework – jsonapi == 2.0.0 – beta . 2

Now add everything to INSTALLED_APPS in settings.py .

INSTALLED_APPS = [<br /><br />

…<br /><br />

‘django.contrib.staticfiles’,<br /><br />

‘corsheaders’,<br /><br />

‘rest_framework’,<br /><br />

‘rest_framework.authtoken’,<br /><br />

‘rest_framework_json_api’,<br /><br />

‘todo'<br /><br />

] 1 2 3 4 5 6 7 8 9 INSTALLED_APPS = [ . . . ‘django.contrib.staticfiles’ , ‘corsheaders’ , ‘rest_framework’ , ‘rest_framework.authtoken’ , ‘rest_framework_json_api’ , ‘todo’ ]

We need the django-cors-headers package to add Cross Origin Resource Sharing headers to our API responses. This will allow our Ember development server running on localhost:4200 to talk to the Django development server on localhost:8000 . Add the middleware from that package to MIDDLEWARE_CLASSES :

MIDDLEWARE_CLASSES = [<br /><br />

…<br /><br />

‘django.contrib.sessions.middleware.SessionMiddleware’,<br /><br />

‘corsheaders.middleware.CorsMiddleware’,<br /><br />

‘django.middleware.common.CommonMiddleware’,<br /><br />

…<br /><br />

] 1 2 3 4 5 6 7 MIDDLEWARE_CLASSES = [ . . . ‘django.contrib.sessions.middleware.SessionMiddleware’ , ‘corsheaders.middleware.CorsMiddleware’ , ‘django.middleware.common.CommonMiddleware’ , . . . ]

And set the CORS_ORIGIN_ALLOW_ALL setting to True (this will be fine for development, but don’t run it like that in production):

CORS_ORIGIN_ALLOW_ALL = True 1 CORS_ORIGIN_ALLOW_ALL = True

Now add the Django Rest Framework settings to settings.py . Most of this is overriding Django Rest Framework defaults with classes from the djangorestframework-jsonapi , which make Django Rest Framework conform to the JSON API specification that Ember Data expects. We also set TokenAuthentication as a default authentication class:

REST_FRAMEWORK = {<br /><br />

‘DEFAULT_AUTHENTICATION_CLASSES’: (<br /><br />

‘rest_framework.authentication.TokenAuthentication’,<br /><br />

‘rest_framework.authentication.SessionAuthentication’,<br /><br />

),<br /><br />

‘PAGE_SIZE’: 10,<br /><br />

‘EXCEPTION_HANDLER’: ‘rest_framework_json_api.exceptions.exception_handler’,<br /><br />

‘DEFAULT_PAGINATION_CLASS’:<br /><br />

‘rest_framework_json_api.pagination.PageNumberPagination’,<br /><br />

‘DEFAULT_PARSER_CLASSES’: (<br /><br />

‘rest_framework_json_api.parsers.JSONParser’,<br /><br />

‘rest_framework.parsers.FormParser’,<br /><br />

‘rest_framework.parsers.MultiPartParser'<br /><br />

),<br /><br />

‘DEFAULT_RENDERER_CLASSES’: (<br /><br />

‘rest_framework_json_api.renderers.JSONRenderer’,<br /><br />

‘rest_framework.renderers.BrowsableAPIRenderer’,<br /><br />

),<br /><br />

‘DEFAULT_METADATA_CLASS’: ‘rest_framework_json_api.metadata.JSONAPIMetadata’,<br /><br />

} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 REST_FRAMEWORK = { ‘DEFAULT_AUTHENTICATION_CLASSES’ : ( ‘rest_framework.authentication.TokenAuthentication’ , ‘rest_framework.authentication.SessionAuthentication’ , ) , ‘PAGE_SIZE’ : 10 , ‘EXCEPTION_HANDLER’ : ‘rest_framework_json_api.exceptions.exception_handler’ , ‘DEFAULT_PAGINATION_CLASS’ : ‘rest_framework_json_api.pagination.PageNumberPagination’ , ‘DEFAULT_PARSER_CLASSES’ : ( ‘rest_framework_json_api.parsers.JSONParser’ , ‘rest_framework.parsers.FormParser’ , ‘rest_framework.parsers.MultiPartParser’ ) , ‘DEFAULT_RENDERER_CLASSES’ : ( ‘rest_framework_json_api.renderers.JSONRenderer’ , ‘rest_framework.renderers.BrowsableAPIRenderer’ , ) , ‘DEFAULT_METADATA_CLASS’ : ‘rest_framework_json_api.metadata.JSONAPIMetadata’ , }

Implementing the Django TodoItem model

With the basic project skeleton in place, we can create a model for todo items. Open models.py in the todo app and implement the following:

from __future__ import unicode_literals<br /><br />

from django.db import models</p><br />

<p>class TodoItem(models.Model):<br /><br />

label = models.CharField(max_length=512)<br /><br />

text = models.TextField(null=True)<br /><br />

done = models.BooleanField(default=False)</p><br />

<p> class JSONAPIMeta:<br /><br />

resource_name = “todos” 1 2 3 4 5 6 7 8 9 10 11 from __future__ import unicode_literals from django . db import models class TodoItem ( models . Model ) : label = models . CharField ( max_length = 512 ) text = models . TextField ( null = True ) done = models . BooleanField ( default = False ) class JSONAPIMeta : resource_name = “todos”

Creating a serializer for TodoItem

Because we are exposing the model with Django Rest Framework, it is going to need a serializer. Make a file called serializers.py in the todo app directory. It should look like this:

from rest_framework import serializers<br /><br />

from models import TodoItem</p><br />

<p>class TodoItemSerializer(serializers.ModelSerializer):<br /><br />

class Meta:<br /><br />

model = TodoItem<br /><br />

fields = (‘label’, ‘text’, ‘done’) 1 2 3 4 5 6 7 8 from rest_framework import serializers from models import TodoItem class TodoItemSerializer ( serializers . ModelSerializer ) : class Meta : model = TodoItem fields = ( ‘label’ , ‘text’ , ‘done’ )

Exposing TodoItem as an API

We can take advantage of standard Django Rest Framework functionality to expose REST endpoints for the model. In views.py in the todo app, add the following:

from rest_framework import viewsets<br /><br />

from models import TodoItem<br /><br />

from serializers import TodoItemSerializer</p><br />

<p>class TodoItemViewSet(viewsets.ModelViewSet):<br /><br />

“””<br /><br />

API endpoint that allows TodoItems to be CRUDed.<br /><br />

“””<br /><br />

queryset = TodoItem.objects.all()<br /><br />

serializer_class = TodoItemSerializer 1 2 3 4 5 6 7 8 9 10 11 from rest_framework import viewsets from models import TodoItem from serializers import TodoItemSerializer class TodoItemViewSet ( viewsets . ModelViewSet ) : “”” API endpoint that allows TodoItems to be CRUDed. “”” queryset = TodoItem . objects . all ( ) serializer_class = TodoItemSerializer

Before we can actually call the endpoints, we need to register the ViewSet in urls.py .

In todo_django/urls.py , register it as follows:

from django.conf.urls import url, include<br /><br />

from django.contrib import admin</p><br />

<p>from rest_framework import routers<br /><br />

from todo.views import TodoItemViewSet</p><br />

<p>router = routers.DefaultRouter(trailing_slash=False)<br /><br />

router.register(“todos”, TodoItemViewSet)</p><br />

<p>urlpatterns = [<br /><br />

url(r’^api/’, include(router.urls)),<br /><br />

…<br /><br />

] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 from django . conf . urls import url , include from django . contrib import admin from rest_framework import routers from todo . views import TodoItemViewSet router = routers . DefaultRouter ( trailing_slash = False ) router . register ( “todos” , TodoItemViewSet ) urlpatterns = [ url ( r ‘^api/’ , include ( router . urls ) ) , . . . ]

One important point: when instantiating the DefaultRouter , make sure to pass the trailing_slashes=False argument. Otherwise Django will try to redirect calls to /api/todos to /api/todos/ , which confuses Ember Data.

As usual when we create a new Django model, we need to migrate:

$ python manage.py makemigrations<br /><br />

$ python manage.py migrate 1 2 $ python manage . py makemigrations $ python manage . py migrate

Now run the Django development server and go to http://localhost:8000/api/todos . You should see the standard Django Rest Framework browseable API screen.

Generating an Ember scaffold with ember-cli-scaffold

We can use the pretty cool ember-cli-scaffold to automatically generate most of what we need to CRUD todo items on the Ember side. From inside the todo-ember directory, run this command:

$ ember g scaffold todo label:string text:string done:boolean 1 $ ember g scaffold todo label : string text : string done : boolean

You should get a printout of everything that was generated. As you can see, it generated a model, routes and templates for us. You can look through the generated files to see what is going on in them.

Something that sucks about the scaffold is that it generated input text components for the boolean attribute on the model. We can change that easily enough.

Open the handlebars template in todos/-form.hbs and find the form component linked to the model’s done component. It should look like this:

{{input type=”text” value=model.done}} 1 { { input type = “text” value = model . done } }

Just change it to this:

{{input type=”checkbox” checked=model.done}} 1 { { input type = “checkbox” checked = model . done } }

It also automatically set up a Mirage mock api server to test against, but we are going to use the Django development server, so we don’t need that in our project. Get rid of it like so:

$ npm uninstall ember-cli-mirage –save-dev 1 $ npm uninstall ember – cli – mirage — save – dev

And it generated an adapter in adapters/todos.js that we won’t need, so delete that too.

Adding an application adapter

Ember Data uses adapters to take requests for information from the store and translate them into calls to the persistence layer. We will create an application adapter that Ember Data will use for all API calls by default:

$ ember g adapter application 1 $ ember g adapter application

Open up adapters/application.js and add the following:

import DS from ’ember-data’;<br /><br />

import ENV from ‘todo-ember/config/environment’;</p><br />

<p>export default DS.JSONAPIAdapter.extend({<br /><br />

host: ENV.host,<br /><br />

namespace: ‘api'<br /><br />

}); 1 2 3 4 5 6 7 import DS from ’ember-data’ ; import ENV from ‘todo-ember/config/environment’ ; export default DS . JSONAPIAdapter . extend ( { host : ENV . host , namespace : ‘api’ } ) ;

You’ll notice that we have imported ENV from todo-ember/config/environment.js . This is the Ember equivalent of Django’s settings.py where we can set up different options for when the app is running in dev, test or production.

Let’s set values for ENV.host in there. We’re pointing to the Django development server in development mode:

module.exports = function(environment) {<br /><br />

var ENV = {<br /><br />

host: ‘https://somehost.com’,<br /><br />

…<br /><br />

}<br /><br />

};</p><br />

<p> if (environment === ‘development’) {<br /><br />

ENV.host = ‘http://localhost:8000’;<br /><br />

}</p><br />

<p>…</p><br />

<p>} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 module . exports = function ( environment ) { var ENV = { host : ‘https://somehost.com’ , . . . } } ; if ( environment === ‘development’ ) { ENV . host = ‘http://localhost:8000’ ; } . . . }

With that in place, the Ember development server will direct all its API calls to localhost:8000 where your Django development server is running.

Start both servers now and in your browser go to http://localhost:4200/todos . You should find that you can create, read, update and delete records on the server from your Ember frontend. Keep an eye on the Django dev server output to see the requests going through.

Setting up Django for token authentication

So far we’ve built a lot of functionality with nor much code, but we’ve got a problem: anybody can access the server and change our data. Let’s fix that so that only registered users can access the todos route.

We are going to use the token authentication mechanism that comes with Django Rest Framework.

First we will add endpoint in Django that our Ember application can call to receive a token that it will use to validate future API calls. Edit urls.py :

from rest_framework.authtoken.views import obtain_auth_token<br /><br />

…</p><br />

<p>urlpatterns = [<br /><br />

url(r’^api-auth-token/’, obtain_auth_token),<br /><br />

…<br /><br />

] 1 2 3 4 5 6 7 from rest_framework . authtoken . views import obtain_auth _ token . . . urlpatterns = [ url ( r ‘^api-auth-token/’ , obtain_auth_token ) , . . . ]

How does this endpoint actually work?

We are going to set up our Ember application to POST some JSON to the endpoint. The JSON will contain a username and password. If the username and password are correct, the endpoint will return a 200 response containing a token.

If the username and password are not correct, it will return a 400 response with an error message. You can try it out with CURL.

First, let’s see what it does with bad credentials:

$ curl -H “Content-Type: application/json” -X POST -d ‘{“username”:”badusername”,”password”:”badpassword”}’ http://localhost:8000/api-auth-token/<br /><br />

[{“status”:”400″,”source”:{“pointer”:”/data/attributes/non_field_errors”},”detail”:”Unable to log in with provided credentials.”}] 1 2 $ curl – H “Content-Type: application/json” – X POST – d ‘{“username”:”badusername”,”password”:”badpassword”}’ http : //localhost:8000/api-auth-token/ [ { “status” : “400” , “source” : { “pointer” : “/data/attributes/non_field_errors” } , “detail” : “Unable to log in with provided credentials.” } ]

Now with good credentials:

$ curl -H “Content-Type: application/json” -X POST -d ‘{“username”:”goodusername”,”password”:”goodpassword”}’ http://localhost:8000/api-auth-token/<br /><br />

{“token”:”e5d92c005934e4034b8335e03ee836fae4ceecfd”} 1 2 $ curl – H “Content-Type: application/json” – X POST – d ‘{“username”:”goodusername”,”password”:”goodpassword”}’ http : //localhost:8000/api-auth-token/ { “token” : “e5d92c005934e4034b8335e03ee836fae4ceecfd” }

We will use an Ember addon called Ember Simple Auth to store this token and add it as a header to future API calls.

Setting up Ember Simple Auth

In the Ember project, add the following settings to environment.js . They control the behaviour of Ember Simple Auth:

ENV[’ember-simple-auth’] = {<br /><br />

authenticationRoute: ‘login’,<br /><br />

routeAfterAuthentication: ‘todos’,<br /><br />

routeIfAlreadyAuthenticated: ‘todos'<br /><br />

}; 1 2 3 4 5 ENV [ ’ember-simple-auth’ ] = { authenticationRoute : ‘login’ , routeAfterAuthentication : ‘todos’ , routeIfAlreadyAuthenticated : ‘todos’ } ;

The code is quite self explanatory, but if you don’t get it right now, relax. You will see what those settings are used for later when we start adding route mixins.

Now generate an application controller (you’ll need to generate a route first):

$ ember g route application<br /><br />

$ ember g controller application 1 2 $ ember g route application $ ember g controller application

And add the following code to it:

import Ember from ’ember’;</p><br />

<p>export default Ember.Controller.extend({<br /><br />

session: Ember.inject.service(‘session’),</p><br />

<p> actions: {<br /><br />

invalidateSession() {<br /><br />

this.get(‘session’).invalidate();<br /><br />

}<br /><br />

}<br /><br />

}); 1 2 3 4 5 6 7 8 9 10 11 import Ember from ’ember’ ; export default Ember . Controller . extend ( { session : Ember . inject . service ( ‘session’ ) , actions : { invalidateSession ( ) { this . get ( ‘session’ ) . invalidate ( ) ; } } } ) ;

We are implementing the invalidateSession action on this controller so we can have a logout link on every page. Add the following snippet to the application template in application.hbs :

{{#if session.isAuthenticated}}<br /><br />

<a {{action ‘invalidateSession’}}>Logout</a><br /><br />

{{else}}<br /><br />

{{#link-to ‘login’}}Login{{/link-to}}<br /><br />

{{/if}} 1 2 3 4 5 { { # if session . isAuthenticated } } <a { { action ‘invalidateSession’ } } > Logout </a> { { else } } { { # link – to ‘login’ } } Login { { / link – to } } { { / if } }

Creating the login form and controller

Let’s generate an Ember route called login where our login form will live.

$ ember g route login 1 $ ember g route login

While we’re at it, let’s generate a controller for the route:

$ ember g controller login 1 $ ember g controller login

Open the template for the route – login.hbs – and add a simple login form:

<form {{action ‘authenticate’ on=’submit’}}><br /><br />

<p><br /><br />

<label for=”username”>Login</label><br /><br />

{{input id=’username’ placeholder=’Enter Login’ value=username}}<br /><br />

</p><br /><br />

<p><br /><br />

<label for=”password”>Password</label><br /><br />

{{input id=’password’ placeholder=’Enter Password’ type=’password’ value=password}}<br /><br />

</p><br /><br />

<p><br /><br />

<button type=”submit”>Login</button><br /><br />

</p><br /><br />

{{#if error}}<br /><br />

<p>{{error}}</p><br /><br />

{{/if}}<br /><br />

</form> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <form { { action ‘authenticate’ on = ‘submit’ } } > <p> <label for = “username” > Login </label> {{input id=’username’ placeholder=’Enter Login’ value=username}} </p> <p> <label for = “password” > Password </label> {{input id=’password’ placeholder=’Enter Password’ type=’password’ value=password}} </p> <p> <button type = “submit” > Login </button> </p> {{#if error}} <p> {{error}} </p> {{/if}} </form>

This won’t work until we implement the authenticate action on the controller, so open up controllers/login.js and add the following:

import Ember from ’ember’;</p><br />

<p>export default Ember.Controller.extend({<br /><br />

session: Ember.inject.service(‘session’),</p><br />

<p> actions: {<br /><br />

authenticate() {<br /><br />

let { username, password } = this.getProperties(‘username’, ‘password’);<br /><br />

this.get(‘session’).authenticate(‘authenticator:drf-token-authenticator’, username, password).catch((reason) => {<br /><br />

this.set(‘error’, reason);<br /><br />

});<br /><br />

}<br /><br />

}<br /><br />

}); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import Ember from ’ember’ ; export default Ember . Controller . extend ( { session : Ember . inject . service ( ‘session’ ) , actions : { authenticate ( ) { let { username , password } = this . getProperties ( ‘username’ , ‘password’ ) ; this . get ( ‘session’ ) . authenticate ( ‘authenticator:drf-token-authenticator’ , username , password ) . catch ( ( reason ) = > { this . set ( ‘error’ , reason ) ; } ) ; } } } ) ;

The code in this file deserves some explanation. At the top of the controller, we are injecting the Ember Simple Auth session service, which manages session state for the application.

Then, in the authenticate action, we are calling authenticate on the service. You have probably noticed that the first argument to authenticate is drf-token-authenticator . This refers to a custom Ember Simple Auth authenticator that we have not implemented yet, so let’s do that.

Implementing a custom authenticator

Inside your ember app directory, make a directory called authenticators . Inside that directory, make a file called drf-token-authenticator.js This is where our custom authenticator will live.

Add the following code to the file:

import Ember from ’ember’;<br /><br />

import Base from ’ember-simple-auth/authenticators/base’;<br /><br />

import ENV from ‘todo-ember/config/environment’;</p><br />

<p>export default Base.extend({<br /><br />

restore(data) {<br /><br />

return new Ember.RSVP.Promise((resolve, reject) => {<br /><br />

if (!Ember.isEmpty(data.token)) {<br /><br />

resolve(data);<br /><br />

} else {<br /><br />

reject();<br /><br />

}<br /><br />

});<br /><br />

},</p><br />

<p> authenticate(username, password) {<br /><br />

return new Ember.RSVP.Promise((resolve, reject) => {<br /><br />

Ember.$.ajax({<br /><br />

url: ENV.host + ‘/api-auth-token/’,<br /><br />

type: ‘POST’,<br /><br />

data: JSON.stringify({<br /><br />

username: username,<br /><br />

password: password<br /><br />

}),<br /><br />

contentType: ‘application/json;charset=utf-8’,<br /><br />

dataType: ‘json'<br /><br />

}).then((response) => {<br /><br />

Ember.run(function() {<br /><br />

resolve({<br /><br />

token: response.token<br /><br />

});<br /><br />

});<br /><br />

}, (xhr, status, error) => {<br /><br />

var response = xhr.responseText;<br /><br />

Ember.run(function() {<br /><br />

reject(response);<br /><br />

});<br /><br />

});<br /><br />

});<br /><br />

},<br /><br />

}); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import Ember from ’ember’ ; import Base from ’ember-simple-auth/authenticators/base’ ; import ENV from ‘todo-ember/config/environment’ ; export default Base . extend ( { restore ( data ) { return new Ember . RSVP . Promise ( ( resolve , reject ) = > { if ( ! Ember . isEmpty ( data . token ) ) { resolve ( data ) ; } else { reject ( ) ; } } ) ; } , authenticate ( username , password ) { return new Ember . RSVP . Promise ( ( resolve , reject ) = > { Ember . $ . ajax ( { url : ENV . host + ‘/api-auth-token/’ , type : ‘POST’ , data : JSON . stringify ( { username : username , password : password } ) , contentType : ‘application/json;charset=utf-8’ , dataType : ‘json’ } ) . then ( ( response ) = > { Ember . run ( function ( ) { resolve ( { token : response . token } ) ; } ) ; } , ( xhr , status , error ) = > { var response = xhr . responseText ; Ember . run ( function ( ) { reject ( response ) ; } ) ; } ) ; } ) ; } , } ) ;

A few words of explanation are required:

We are extending the Ember base authenticator and implementing the restore and authenticate methods.

restore “restores the session from a session data object. This method is invoked by the session either on application startup if session data is restored from the session store.” If we don’t implement this then if we log in and then refresh the page for instance we will be kicked back to the login screen.

authenticate is where we call the /api-auth-token/ endpoint we created in Django. The method returns an Ember Promise that resolves or rejects based on the response to the API call.

The API call itself is made with jQuery, which is embedded into Ember. Notice that we are using the ENV.host that we placed in environment.js earlier to direct the AJAX request to the proper endpoint.

If you run the application now and go to http://localhost:4200/login you should see a login form. Try to log in with an invalid username and password first. You should see the error JSON from the server underneath the form. Then try it with a valid username and password. You should see the “login” link at the top of the page change to a “logout” link.

Securing the API

So far so good, but we’re not actually preventing anyone from sending unauthenticated requests to the API. To do that, we need to edit the ViewSet in Django:

…<br /><br />

from rest_framework.authentication import TokenAuthentication<br /><br />

from rest_framework.permissions import IsAuthenticated</p><br />

<p>class TodoItemViewSet(viewsets.ModelViewSet):<br /><br />

“””<br /><br />

API endpoint that allows TodoItems to be CRUDed.<br /><br />

“””<br /><br />

queryset = TodoItem.objects.all()<br /><br />

serializer_class = TodoItemSerializer<br /><br />

authentication_classes = (TokenAuthentication,)<br /><br />

permission_classes = (IsAuthenticated,) 1 2 3 4 5 6 7 8 9 10 11 12 . . . from rest_framework . authentication import TokenAuthentication from rest_framework . permissions import IsAuthenticated class TodoItemViewSet ( viewsets . ModelViewSet ) : “”” API endpoint that allows TodoItems to be CRUDed. “”” queryset = TodoItem . objects . all ( ) serializer_class = TodoItemSerializer authentication_classes = ( TokenAuthentication , ) permission_classes = ( IsAuthenticated , )

Notice that we added two new properties – authentication_classes and permission_classes .

Right now if you go to http://localhost:4200/todos you will find that our application is broken. Django expects each incoming request on the /todos/ route to have a valid Authorization header with the value Token my_token_value . In order to automatically add that header to outgoing requests, we need to write a custom authorizer.

Writing a custom authorizer

Authorizers are components of Ember Simple Auth that add authorization information to requests made by Ember.

Make a directory inside your app called authorizers and make a file inside that directory called drf-token-authorizer.js . Add the code below to that file:

import Ember from ’ember’;<br /><br />

import Base from ’ember-simple-auth/authorizers/base’;</p><br />

<p>export default Base.extend({<br /><br />

session: Ember.inject.service(‘session’),</p><br />

<p> authorize: function(sessionData, block) {<br /><br />

if (this.get(‘session.isAuthenticated’) && !Ember.isEmpty(sessionData.token)) {<br /><br />

block(‘Authorization’, ‘Token ‘ + sessionData.token);<br /><br />

}<br /><br />

}<br /><br />

}); 1 2 3 4 5 6 7 8 9 10 11 12 import Ember from ’ember’ ; import Base from ’ember-simple-auth/authorizers/base’ ; export default Base . extend ( { session : Ember . inject . service ( ‘session’ ) , authorize : function ( sessionData , block ) { if ( this . get ( ‘session.isAuthenticated’ ) && ! Ember . isEmpty ( sessionData . token ) ) { block ( ‘Authorization’ , ‘Token ‘ + sessionData . token ) ; } } } ) ;

We are extending the base authorizer from Ember Simple Auth and implementing authorize on it. This checks that the session is authenticated, grabs the token from the passed in sessionData variable, and adds it as a header by calling the passed in block with the header name and the header value.

To make sure that the header is added to all API calls that Ember makes, we need to modify the application adapter. Open adapters/application.js and change it from what we had earlier:

import DS from ’ember-data’;<br /><br />

import ENV from ‘todo-ember/config/environment’;<br /><br />

import DataAdapterMixin from ’ember-simple-auth/mixins/data-adapter-mixin’;</p><br />

<p>export default DS.JSONAPIAdapter.extend(DataAdapterMixin, {<br /><br />

host: ENV.host,<br /><br />

namespace: ‘api’,<br /><br />

authorizer: ‘authorizer:drf-token-authorizer'<br /><br />

}); 1 2 3 4 5 6 7 8 9 import DS from ’ember-data’ ; import ENV from ‘todo-ember/config/environment’ ; import DataAdapterMixin from ’ember-simple-auth/mixins/data-adapter-mixin’ ; export default DS . JSONAPIAdapter . extend ( DataAdapterMixin , { host : ENV . host , namespace : ‘api’ , authorizer : ‘authorizer:drf-token-authorizer’ } ) ;

You can see here that we are importing the DataAdapterMixin from Ember Simple Auth and adding it to the adapter as a mixin. We also need to set up authorizer to point to our custom authorizer.

At this point, if you try your application again you should be able to successfully view, edit, create and delete todo list items.

Adding Ember Simple Auth route mixins

A problem with our application right now is that it does not prevent you from accessing the todos route without logging in (although after the last change the API call to fetch the todo items won’t work). We would like users who go straight to todos without logging in to be sent instead to the login route.

It doesn’t automatically send you to todos after login either.

And it would be great if users who go to login while already logged were sent back to todos .

It is quite simple to achieve these things using route mixins. The first one we need to add is in the application route.

Open routes/application.js and change it so it looks like this:

import Ember from ’ember’;<br /><br />

import ApplicationRouteMixin from ’ember-simple-auth/mixins/application-route-mixin’;</p><br />

<p>export default Ember.Route.extend(ApplicationRouteMixin, {</p><br />

<p>}); 1 2 3 4 5 6 import Ember from ’ember’ ; import ApplicationRouteMixin from ’ember-simple-auth/mixins/application-route-mixin’ ; export default Ember . Route . extend ( ApplicationRouteMixin , { } ) ;

This will enable Ember Simple Auth to automatically change the route based on the authentication state.

Now we need to protect the todos route so that it cannot be accessed without being logged in. Open each route in routes/todos/ and add the AuthenticatedRouteMixin . For example, routes/todos/edit.js should look like this:

import Ember from ’ember’;<br /><br />

import SaveModelMixin from ‘todo-ember/mixins/todos/save-model-mixin’;<br /><br />

import AuthenticatedRouteMixin from ’ember-simple-auth/mixins/authenticated-route-mixin’;</p><br />

<p>export default Ember.Route.extend(SaveModelMixin, AuthenticatedRouteMixin, {<br /><br />

}); 1 2 3 4 5 6 import Ember from ’ember’ ; import SaveModelMixin from ‘todo-ember/mixins/todos/save-model-mixin’ ; import AuthenticatedRouteMixin from ’ember-simple-auth/mixins/authenticated-route-mixin’ ; export default Ember . Route . extend ( SaveModelMixin , AuthenticatedRouteMixin , { } ) ;

Do the same for the nested index and new routes.

If you go to http://localhost:4200/todos now without being logged in you will be kicked back to the login page. Where you go depends on the value for authenticationRoute that we added to environment.js earlier. routeAfterAuthentication and routeIfAlreadyAuthenticated control where you do when you log in and when you come back to the site after already being logged in.

The last mixin we need to add is the UnauthenticatedRouteMixin in the login route. It prevents authenticated users from seeing the login page. Here is what your routes/login.js should look like after you add it.

import Ember from ’ember’;<br /><br />

import UnauthenticatedRouteMixin from ’ember-simple-auth/mixins/unauthenticated-route-mixin’;</p><br />

<p>export default Ember.Route.extend(UnauthenticatedRouteMixin, {<br /><br />

}); 1 2 3 4 5 import Ember from ’ember’ ; import UnauthenticatedRouteMixin from ’ember-simple-auth/mixins/unauthenticated-route-mixin’ ; export default Ember . Route . extend ( UnauthenticatedRouteMixin , { } ) ;

Registering users

At the moment all we can do is log in with existing users. Nobody else can register an account. Let’s fix that.

We’re going to need a registration endpoint in Django that Ember can use. Here is a one I pulled out of an old project. It’s not going to win any beauty contests, but it does the trick:

@require_http_methods([“POST”])<br /><br />

@csrf_exempt<br /><br />

def register(request):<br /><br />

“””<br /><br />

API endpoint to register a new user.<br /><br />

“””<br /><br />

try:<br /><br />

payload = json.loads(request.body)<br /><br />

except ValueError:<br /><br />

return JsonResponse({“error”: “Unable to parse request body”}, status=400)</p><br />

<p> form = RegistrationForm(payload)</p><br />

<p> if form.is_valid():<br /><br />

user = User.objects.create_user(form.cleaned_data[“username”],<br /><br />

form.cleaned_data[“email”],<br /><br />

form.cleaned_data[“password”])<br /><br />

user.save()</p><br />

<p> return JsonResponse({“success”: “User registered.”}, status=201)</p><br />

<p> return HttpResponse(form.errors.as_json(), status=400, content_type=”application/json”) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @ require_http_methods ( [ “POST” ] ) @ csrf_exempt def register ( request ) : “”” API endpoint to register a new user. “”” try : payload = json . loads ( request . body ) except ValueError : return JsonResponse ( { “error” : “Unable to parse request body” } , status = 400 ) form = RegistrationForm ( payload ) if form . is_valid ( ) : user = User . objects . create_user ( form . cleaned_data [ “username” ] , form . cleaned_data [ “email” ] , form . cleaned_data [ “password” ] ) user . save ( ) return JsonResponse ( { “success” : “User registered.” } , status = 201 ) return HttpResponse ( form . errors . as_json ( ) , status = 400 , content_type = “application/json” )

It relies on a user registration form that takes a username, email address, a password and a copy of the password to make sure they are the same. Here it is (it lives in forms.py ):

class RegistrationForm(forms.Form):<br /><br />

“””<br /><br />

Register a new user.<br /><br />

“””<br /><br />

username = forms.CharField(max_length=30, min_length=4, label=”Username”)<br /><br />

email = forms.EmailField()<br /><br />

password = forms.CharField(widget=forms.PasswordInput(), min_length=5, label=”Password”)<br /><br />

confirm_password = forms.CharField(widget=forms.PasswordInput(), min_length=5, label=”Confirm Password”)</p><br />

<p> def clean_username(self):<br /><br />

username = self.cleaned_data[“username”]</p><br />

<p> try:<br /><br />

user = User.objects.get(username=username)<br /><br />

except User.DoesNotExist:<br /><br />

return username</p><br />

<p> raise forms.ValidationError(<br /><br />

“The username %s is already taken.” % username)</p><br />

<p> def clean(self):<br /><br />

“””<br /><br />

Make sure that the two passwords match.<br /><br />

“””<br /><br />

password = self.cleaned_data.get(“password”, None)<br /><br />

confirm_password = self.cleaned_data.get(“confirm_password”, None)</p><br />

<p> if password == confirm_password:<br /><br />

return self.cleaned_data</p><br />

<p> raise forms.ValidationError(“The passwords do not match.”) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class RegistrationForm ( forms . Form ) : “”” Register a new user. “”” username = forms . CharField ( max_length = 30 , min_length = 4 , label = “Username” ) email = forms . EmailField ( ) password = forms . CharField ( widget = forms . PasswordInput ( ) , min_length = 5 , label = “Password” ) confirm_password = forms . CharField ( widget = forms . PasswordInput ( ) , min_length = 5 , label = “Confirm Password” ) def clean_username ( self ) : username = self . cleaned_data [ “username” ] try : user = User . objects . get ( username = username ) except User . DoesNotExist : return username raise forms . ValidationError ( “The username %s is already taken.” % username ) def clean ( self ) : “”” Make sure that the two passwords match. “”” password = self . cleaned_data . get ( “password” , None ) confirm_password = self . cleaned_data . get ( “confirm_password” , None ) if password == confirm_password : return self . cleaned_data raise forms . ValidationError ( “The passwords do not match.” )

This is all pretty standard Django stuff, but note the use of JsonResponse and the use of the HTTP status code to signal the outcome of the request.

The view has to be added to urls.py , naturally:

…<br /><br />

url(r’^api-register/’, register),<br /><br />

… 1 2 3 . . . url ( r ‘^api-register/’ , register ) , . . .

Now that the Django side is ready, let’s take care of the Ember side. We have to generate a register route and controller.

$ ember g route register<br /><br />

$ ember g controller register 1 2 $ ember g route register $ ember g controller register

Then we can put a registration form in register.hbs .

{{#if signupComplete}}<br /><br />

<p>Signup complete.</p><br /><br />

{{else}}<br /><br />

<form {{action ‘register’ on=’submit’}}><br /><br />

<p><br /><br />

<label for=”username”>Login</label><br /><br />

{{input id=’username’ placeholder=’Enter Login’ value=username}}<br /><br />

</p><br /><br />

<p><br /><br />

<label for=”email”>Email</label><br /><br />

{{input id=’email’ placeholder=’Enter Email’ value=email}}<br /><br />

</p><br /><br />

<p><br /><br />

<label for=”password”>Password</label><br /><br />

{{input id=’password’ placeholder=’Enter Password’ type=’password’ value=password}}<br /><br />

</p><br /><br />

<p><br /><br />

<label for=”confirm_password”>Confirm Password</label><br /><br />

{{input id=’confirm_password’ placeholder=’Confirm Password’ type=’password’ value=confirm_password}}<br /><br />

</p><br /><br />

<p><br /><br />

<button type=”submit”>Sign Up</button><br /><br />

</p></p><br />

<p> {{#if error}}<br /><br />

<p>{{error}}</p><br /><br />

{{/if}}<br /><br />

</form><br /><br />

{{/if}} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 { { # if signupComplete } } <p> Signup complete. </p> {{else}} <form { { action ‘register’ on = ‘submit’ } } > <p> <label for = “username” > Login </label> {{input id=’username’ placeholder=’Enter Login’ value=username}} </p> <p> <label for = “email” > Email </label> {{input id=’email’ placeholder=’Enter Email’ value=email}} </p> <p> <label for = “password” > Password </label> {{input id=’password’ placeholder=’Enter Password’ type=’password’ value=password}} </p> <p> <label for = “confirm_password” > Confirm Password </label> {{input id=’confirm_password’ placeholder=’Confirm Password’ type=’password’ value=confirm_password}} </p> <p> <button type = “submit” > Sign Up </button> </p> {{#if error}} <p> {{error}} </p> {{/if}} </form> { { / if } }

Modify the autogenerated register route to include the UnauthenticatedRouteMixin :

import Ember from ’ember’;<br /><br />

import UnauthenticatedRouteMixin from ’ember-simple-auth/mixins/unauthenticated-route-mixin’;</p><br />

<p>export default Ember.Route.extend(UnauthenticatedRouteMixin, {<br /><br />

}); 1 2 3 4 5 import Ember from ’ember’ ; import UnauthenticatedRouteMixin from ’ember-simple-auth/mixins/unauthenticated-route-mixin’ ; export default Ember . Route . extend ( UnauthenticatedRouteMixin , { } ) ;

Now we will implement the register action on the register controller that the registration form uses. It looks like this:

import Ember from ’ember’;<br /><br />

import ENV from ‘todo-ember/config/environment’;</p><br />

<p>export default Ember.Controller.extend({</p><br />

<p> actions: {<br /><br />

register() {<br /><br />

let {username, email, password, confirm_password} = this.getProperties(<br /><br />

‘username’,<br /><br />

’email’,<br /><br />

‘password’,<br /><br />

‘confirm_password'<br /><br />

);</p><br />

<p> Ember.$.ajax({<br /><br />

url: ENV.host + ‘/api-register/’,<br /><br />

type: ‘POST’,<br /><br />

data: JSON.stringify({<br /><br />

username: username,<br /><br />

email: email,<br /><br />

password: password,<br /><br />

confirm_password: confirm_password<br /><br />

}),<br /><br />

contentType: ‘application/json;charset=utf-8’,<br /><br />

dataType: ‘json'<br /><br />

}).then((response) => {<br /><br />

this.set(‘signupComplete’, true);<br /><br />

}, (xhr, status, error) => {<br /><br />

this.set(‘error’, xhr.responseText);<br /><br />

});<br /><br />

}<br /><br />

}<br /><br />

}); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import Ember from ’ember’ ; import ENV from ‘todo-ember/config/environment’ ; export default Ember . Controller . extend ( { actions : { register ( ) { let { username , email , password , confirm_password } = this . getProperties ( ‘username’ , ’email’ , ‘password’ , ‘confirm_password’ ) ; Ember . $ . ajax ( { url : ENV . host + ‘/api-register/’ , type : ‘POST’ , data : JSON . stringify ( { username : username , email : email , password : password , confirm_password : confirm _ password } ) , contentType : ‘application/json;charset=utf-8’ , dataType : ‘json’ } ) . then ( ( response ) = > { this . set ( ‘signupComplete’ , true ) ; } , ( xhr , status , error ) = > { this . set ( ‘error’ , xhr . responseText ) ; } ) ; } } } ) ;

This follows the same pattern as the login form. We grab the values in the form fields then use JSON.stringify to assemble them into a JSON payload that we POST to the server. The server then returns either 201 Created response or a 400 response with information about what went wrong.

If the success callback is fired, we hide the login form and show the “Signup Complete” message. Otherwise, we write the error message below the form.

In the same was as with the login form, we are just writing the error responses in raw JSON below the registration form. Exactly how you alert the user to errors will depend on your CSS framework and the facilities it provides for, e.g. form element highlighting, etc. In any case, it is a trivial matter to display the errors more nicely on the page, so we won’t waste time with that today.

Now you should be able to register new users and log in with them.

Make a link to the registration form by editing the application.hbs template. Change the line with the “login” link to include a “register” link too:

{{#link-to ‘login’}}Login{{/link-to}} {{#link-to ‘register’}}Register{{/link-to}} 1 { { # link – to ‘login’ } } Login { { / link – to } } { { # link – to ‘register’ } } Register { { / link – to } }

Linking Todo items to users

Right now when users log in they see all the todo items and not just the ones that belong to them. Let’s fix that so that todo items are linked to users and users can only see and edit their own.

Add a foreign key to the TodoItem model that points to the user:

class TodoItem(models.Model):<br /><br />

“””<br /><br />

An item to be done.<br /><br />

“””<br /><br />

user = models.ForeignKey(User)<br /><br />

label = models.CharField(max_length=512)<br /><br />

text = models.TextField(null=True)<br /><br />

done = models.BooleanField(default=False)</p><br />

<p> class JSONAPIMeta:<br /><br />

resource_name = “todos” 1 2 3 4 5 6 7 8 9 10 11 class TodoItem ( models . Model ) : “”” An item to be done. “”” user = models . ForeignKey ( User ) label = models . CharField ( max_length = 512 ) text = models . TextField ( null = True ) done = models . BooleanField ( default = False ) class JSONAPIMeta : resource_name = “todos”

Ass with all model changes, we need to migrate. When we run makemigrations we will be prompted to specify a default value for the user field on existing rows. Just give it the primary key of an existing user:

$ python manage.py makemigrations<br /><br />

You are trying to add a non-nullable field ‘user’ to todoitem without a default; we can’t do that (the database needs something to populate existing rows).<br /><br />

Please select a fix:<br /><br />

1) Provide a one-off default now (will be set on all existing rows)<br /><br />

2) Quit, and let me add a default in models.py<br /><br />

Select an option: 1<br /><br />

Please enter the default value now, as valid Python<br /><br />

The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()<br /><br />

>>> 1 1 2 3 4 5 6 7 8 9 $ python manage . py makemigrations You are trying to add a non – nullable field ‘user’ to todoitem without a default ; we can ‘ t do that ( the database needs something to populate existing rows ) . Please select a fix : 1 ) Provide a one – off default now ( will be set on all existing rows ) 2 ) Quit , and let me add a default in models . py Select an option : 1 Please enter the default value now , as valid Python The datetime and django . utils . timezone modules are available , so you can do e . g . timezone . now ( ) >>> 1

Then apply the generated migration:

$ python manage.py migrate 1 $ python manage . py migrate

To restrict the todo items to the current user, override the get_queryset method on the ViewSet :

def get_queryset(self):<br /><br />

return TodoItem.objects.filter(user=self.request.user) 1 2 def get_queryset ( self ) : return TodoItem . objects . filter ( user = self . request . user )

And remove the queryset :

queryset = TodoItem.objects.all() # DELETE THIS LINE 1 queryset = TodoItem . objects . all ( ) # DELETE THIS LINE

At this point we need to add the base_name to the router to avoid errors:

…<br /><br />

router.register(“todos”, TodoItemViewSet, base_name=”todo”)<br /><br />

… 1 2 3 . . . router . register ( “todos” , TodoItemViewSet , base_name = “todo” ) . . .

When new todo items are created, we want them to be linked to the current user. We can achieve that by overriding perform_create on the ViewSet :

def perform_create(self, serializer):<br /><br />

serializer.save(user=self.request.user) 1 2 def perform_create ( self , serializer ) : serializer . save ( user = self . request . user )

We also want object-level permissions that prevent people from directly accessing or modifying other users’ todo items, so we will implement a custom Django Rest Framework permission class.

You can put the following code anywhere, but I like to put it in a file called permissions.py in the todo app:

from rest_framework import permissions</p><br />

<p>class BelongsToUser(permissions.BasePermission):<br /><br />

def has_object_permission(self, request, view, obj):<br /><br />

return request.user == obj.user 1 2 3 4 5 6 from rest_framework import permissions class BelongsToUser ( permissions . BasePermission ) : def has_object_permission ( self , request , view , obj ) : return request . user == obj . user

This class implements has_object_permission(self, request, view, obj) from the base class and performs a simple check to see if the user on the object is the same as the authenticated user.

Now change permission_classes on the ViewSet to apply this permission:

permission_classes = (IsAuthenticated, BelongsToUser,) 1 permission_classes = ( IsAuthenticated , BelongsToUser , )

That’s it! At this point if you register a bunch of users they will all have their own todo items.

The end

Phew! This has been a pretty long post, but if you have followed along you have gotten over the biggest initial hurdles of working with Ember and Django.

Check out the resources in the next section to learn more.

Resources

The Ember Quickstart Guide should be the first destination in your Ember journey.

The Ember Simple Auth documentation will help you get to grips with this indispensable Ember library.

The Django Rest Framework JSON API package makes linking Ember Data and Django Rest Framework pretty seamless.

Built With Ember showcases the sophisticated user experience that can be achieved with this cool framework.