Token-based authentication with Django and React

This tutorial will show you how to create a simple React app that uses Django Rest Framework’s token authentication to grant or deny users access to certain pages. If you want to see the finished product, check out the github page for this tutorial. This will be our basic workflow:

  1. When a user tries to access the app without authenticating, reroute them to the login page.
  2. After the user enters their credentials, send these credentials to Django Rest Framework (DRF). If the credentials are valid return an authentication token.
  3. Store the token in localStorage so that it can be automatically submitted as a header with future HTTP requests.
  4. Reroute the user from the login page to the app, using the token to grant them access to their data.
  5. When the user clicks the logout button, delete the token in localStorage and reroute them to the login page.

Setting up the backend will be relatively easy. All we have to do is to add a post_save signal handler that creates a Token object whenever a new user object is created. DRF provides us with an obtain_auth_token view, which responds with a token when passed a valid username and password. Then it’s simply a matter of adding that token as a header to our HTTP requests.

Dealing with authentication on the frontend is a little trickier. We will use react-router’s onEnter hooks to check whether a user is authenticated when they try to access certain pages, and reroute them if they aren’t authenticated. We’ll also create a login function that gets the token from the API and stores it in localStorage.

Backend

First, create a file named signals.py in your app directory and add the following signal handler:

#signals.py

from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from rest_framework.authtoken.models import Token

@receiver(post_save, sender=User)
def init_new_user(sender, instance, signal, created, **kwargs):
    if created:
        Token.objects.create(user=instance)

Whenever a new user is created, this signal handler creates a new Token object linked to that user.

If you haven’t already, make sure that signals.py is imported when the app is loaded. To do this, override the ready method in your AppConfig class in apps.py.

#apps.py

from django.apps import AppConfig

class ApiConfig(AppConfig):
    name = 'api'

    def ready(self):
        from . import signals

Last of all, set this as your default app config in your app’s init.py:

#__init__.py

default_app_config = 'api.apps.ApiConfig'

Django now knows to create a new token for each new User object. But the token is useless if our frontend has no way to get to it. Lucky for us, DRF comes with a view that returns a token if you pass it a valid username/password combo. To use it, you first need to add rest_framework.authtoken to the list of INSTALLED_APPS in settings.py, then set the default authentication for DRF as follows:

#settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication'   
    )
}

Now create a custom route for our viewset in views.py that returns user data when an authenticated user sends a request:

#views.py

class UserViewVSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def retrieve(self, request, pk=None):
        if pk == 'i':
            return Response(UserSerializer(request.user,
                context={'request':request}).data)
        return super(UserViewSet, self).retrieve(request, pk)

Last of all, let’s add the obtain_auth_token view to our urlconf:

#urls.py

from rest_framework.authtoken.views import obtain_auth_token

urlpatterns += [ url(r'^obtain-auth-token/$', obtain_auth_token) ]

Okay, enough coding. Open a terminal and let’s see if this works as advertised:

$ curl localhost:8000/users/i/

{"detail":"Authentication credentials were not provided."}

$ curl -X POST -d "username=user&password=pass" localhost:8000/obtain-auth-token/

{"token": "token"}

$ curl -H "Authorization: Token {your token here}" localhost:8000/users/i/

Frontend

We’re now going to create a simple single-page React app that makes use of this authorization backend. We’ll be using jQuery to make the AJAX requests cleaner, but you can use plain Javascript if you prefer. If you aren’t sure how to make React work with a Django backend (or are unsure about plugging in jQuery), check out my earlier tutorial.

//index.jsx

var React = require('react')
var ReactDOM = require('react-dom')
var Router = require('react-router')
var App = require('./app')
var Login = require('./login')
var auth = require('./auth')

function requireAuth(nextState, replace) {
    if (!auth.loggedIn()) {
        replace({ 
            pathname:'/app/login/',
            state: {nextPathname: '/app/'}
        })
    }
}

ReactDOM.render(
    <Router.Router history={Router.browserHistory}>
        <Router.Route path='/app/login/' component={Login} />
        <Router.Route path='/app/' component={App} onEnter={requireAuth} />
    </Router.Router>,
    document.getElementById('app')    
)

When a user tries to access /app/, the requireAuth() function fires. If the user isn’t logged in, it reroutes them to the login component. Not only that, it adds information about the route the user was trying to access to the login component’s state. That means that after the user logs in successfully, you can reroute them to the page they were trying to access in the first place.

Now, let’s create an auth.js file to contain the authentication logic.

//auth.js

module.exports = {
    login: function(username, pass, cb) {
        if (localStorage.token) {
            if (cb) cb(true)
            return
        }
        this.getToken(username, pass, (res) => {
            if (res.authenticated) {
                localStorage.token = res.token
                if (cb) cb(true)
            } else {
                if (cb) cb(false)
            }
        })
    },        
    
    logout: function() {
        delete localStorage.token
    },

    loggedIn: function() {
        return !!localStorage.token
    },

    getToken: function(username, pass, cb) {
        $.ajax({
            type: 'POST',
            url: '/api/obtain-auth-token/',
            data: {
                username: username,
                password: pass
            },
            success: function(res){
                cb({
                    authenticated: true,
                    token: res.token
                })
            }
        })
    }, 
}

Our auth class’s logout() and loggedIn() functions are pretty self-explanatory. The login() function is a little trickier. It takes a username and password, and passes them to the getToken function. This function - you guessed it - get the authentication token from the API.

Here’s what our login component will look like:

//login.jsx

var React = require('react')
var auth = require('./auth')

module.exports = React.createClass({
    contextTypes: {
        router: React.PropTypes.object.isRequired
    },

    handleSubmit: function(e) {
        e.preventDefault()

        var username = this.refs.username.value
        var pass = this.refs.pass.value

        auth.login(username, pass, (loggedIn) => {
            if (loggedIn) {
                this.context.router.replace('/app/')
            }
        })
    },
    
    render: function() {
        return (
            <form onSubmit={this.handleSubmit}>
                <input type="text" placeholder="username" ref="username" />
                <input type="password" placeholder="password" ref="pass" />
                <input type="submit" />
            </form>
        )    
    }
})

If the request was successful and the token has been set, the handleSubmit() function will reroute the user to the /app/ page, which will display their data.

Last of all, here’s the app page. It simply displays a message, followed by the authenticated user’s username.

//app.jsx

var React = require('react')
var auth = require('./auth')

module.exports = React.createClass({
   getInitialState: function() {
        return {'user':[]}
    },

    componentDidMount: function() {
        this.loadUserData()
    },
            
    contextTypes: {
        router: React.PropTypes.object.isRequired
    },

    logoutHandler: function() {
        auth.logout()
        this.context.router.replace('/app/login/')
    },

    loadUserData: function() {
        $.ajax({
            method: 'GET',
            url: '/api/users/i/',
            datatype: 'json',
            headers: {
                'Authorization': 'Token ' + localStorage.token
            },
            success: function(res) {
                this.setState(user: res)
            }.bind(this)
        })
    },

    render: function() {
        return (
            <div>
            <h1>You are now logged in, {this.state.user.username}</h1>
            <button onClick={this.logoutHandler}>Log out</button>
            </div>
        )        
    }
})

This component is pretty straightforward. The important thing to remember is that you need to add an Authorization header to your GET requests, or else DRF will throw an error. This component also includes a logout button that simply deletes the token from localStorage and reroutes the user to the login page.