Recently I’ve been building an app using a combination of React for the frontend and Django for the backend. I wasn’t previously all too familiar with Django, but this seemed like a good opportunity to teach myself, and having experience with Ruby on Rails has made the process a little easier. However, Django and Rails have their fair share of differences, and one of the things which I’ve had the most trouble implementing is user authentication. So I’m sharing what I’ve learned, in the hopes that you too might find it useful!
We’ll be building a demo app, for which you can find the finalized code for the frontend here and for the backend here. Also, I should mention that I’m currently working with Django 2.0.4, and React 16.3.
The Basic Premise: Sessions vs. Tokens
There are many different potential approaches to implementing authentication. Here I’ll just cover two of the most common ones: session authentication (via cookies), and token authentication. There are plenty of articles around the internet differentiating the two, but I’ll give a quick summary of what they are and how they work.
Session authentication is stateful, which means when a user logs in, data pertaining to their authenticated status gets stored either in memory or a database. This data is collectively referred to as a session, and to facilitate its access, a cookie with the user’s session ID is sent back to the client and stored in the browser. Then, next time the client makes a request, that cookie is included in the request and the server searches for a session that matches the cookie’s session ID. If there’s a match, then the backend proceeds to process the request. When the user logs out, another request has to be made to the server so that the relevant session data is destroyed, along with the cookie in the browser’s storage.
Session authentication was for a long time considered the preferred approach, and it remains widely used. However, it suffers from several notable drawbacks. Most relevant to our particular circumstance is the fact that cookies are tied to a particular domain, which leads to significant CORS headaches when the front and back ends are decoupled.
That leads us to token authentication. Tokens are key-value pairs which usually live in the local storage of your browser. In this regard they are similar to cookies — however, where session authentication was stateful, token authorization is stateless, meaning there’s no record kept on the server of which users are logged in, how many tokens have been issued etc. Instead, tokens are generated by means of a complex encryption process which, when reversed and decrypted, authenticates the user.
This is a very broad generalization of the methodology employed by JSON Web Tokens (JWTs for short). JWTs are regarded as the gold standard in authentication right now, so that’s what we’ll be using today. With that said, let’s write some code!
Setting Up Django
First off, we need to set up our virtual environment (if that’s unfamiliar to you, I wrote a whole blog post about it!). I’m going to use pipenv here—make sure you have it installed, then navigate to the directory you want your project to be in and run:
Once that’s done, activate the virtual environment with:
Now we’re going to need to install some packages, including Django, Django REST framework (hereafter referred to as the DRF), Django REST framework JWT, and Django CORS headers. The DRF is what we’ll be layering on top of Django to turn our project into an API, while Django REST framework JWT gives us the ability to use JWT tokens for our app, and Django CORS headers is necessary to avoid CORS issues:
pipenv install django
pipenv install djangorestframework
pipenv install djangorestframework-jwt
pipenv install django-cors-headers
Once this is done, we’re ready to create the Django project. Run the following:
django-admin startproject mysite .
Note the period at the end there — that denotes that we want to create the project with the current directory as as the root, rather than putting it in a new subdirectory. At this point, your project structure should look something like this:
Now we need to adjust some of our project’s settings. Go into
settings.py and make the following modifications:
Let’s examine what we’ve done here: first, we’ve registered the DRF and CORS headers packages with our project by adding them to
INSTALLED_APPS. Then, we added a piece of custom CORS middleware, making sure to place it place it above any middleware that generates responses such as Django’s
CommonMiddleware. We then customized some of the default settings for the DRF: first, we set the
DEFAULT_PERMISSION_CLASSES, which in this case will require a request to be authenticated before it is processed unless specified otherwise. Then, we set the
DEFAULT_AUTHENTICATION_CLASSES, which determines which authentication methods the server will try when it receives a request, in descending order. Finally, we added
localhost:3000 to the
CORS_ORIGIN_WHITELIST, since that’s where the requests from our React app will be coming from.
The DRF JWT package provides us with a default view for decoding received JWTs. We can add that to
And we’re already almost ready to test whether or not this works! But before we can, we need to create a user, and before do that, we need to apply our migrations. Run the following:
python manage.py migrate
Once that’s done, the easiest way to create a new user is with:
python manage.py createsuperuser
Fill in all the fields and you should be good to go. Now if you start your development server:
python manage.py runserver
and navigate to http://localhost:8000/token-auth/, you should see an html form there with username and password fields (this is a convenience provided by the DRF… isn’t it awesome?). Fill in these fields and you should see the JWT itself displayed right there on the page.
This takes care of logging in but we still need our users to be able to sign up. Fortunately, Django comes with a built in
User model that we can use (which is easy enough to customize, should you need to do so). All we need to do is create the view for it. But if we’re making a view, we’re going to need an app to put it in. So let’s do that now:
python manage.py startapp core
I’m calling the app “core”, but you can of course call it whatever you want. Before we do anything else, let’s register the app in
Now we need to take a few steps that might seem circuitous at first but which I promise will make sense by the end. First, we need to create a couple serializers for our
User model. These serializers will be responsible for serializing/unserializing the
User model into and out of various formats, primarily JSON in our case. Go ahead and create a new
core/serializers.py file, and fill it with the following:
The reason we’re making two different serializers for the model is because we’ll be using the
UserSerializerWithToken for handling signups. When a user signs up, we want the response from the server to include both their relevant user data (in this case, just the username), as well as the token, which will be stored in the browser for further authentication. But we don’t need the token every time we request a user’s data — just when signing up. Thus, separate serializers.
That’s the ‘why’, but let’s take a closer look at the code for the ‘how’. Both serializers inherit from
rest_framework.serializers.ModelSerializer, which provides us with a handy shortcut for customizing the serializers according to the model data they’ll be working with (otherwise we’d need to spell out every field by hand). In the internal
Meta class, we indicate which model each serializer will be representing, and which fields from that model we want the serializer to include.
User class doesn’t have an internal ‘token’ field, so for that we do need to define our own custom field. We define the
token variable to be a custom method, then add a
get_token() method which handles the manual creation of a new token. It does this using the default settings for payload and encoding handling provided by the JWT package (the payload is the data being tokenized, in this case the user). Finally, we added the custom ‘token’ field to the
fields variable in our
Meta internal class.
We also need to make sure the serializer recognizes and stores the submitted password, but doesn’t include it in the returned JSON. So we add the ‘password’ field to fields, but above that also specify that the password should be write only. Then, we override the serializer’s
create() method, which determines how the object being serialized gets saved to the database. We do this primarily so that we can call the
set_password() method on the user instance, which is how the password gets properly hashed.
Now we’re ready to start configuring our views in
core/views.py. There are many ways of going about doing this (function-based views, class-based views, or viewsets). Since viewsets can be confusing if you don’t understand what’s happening internally (and since we don’t have enough views here to reap their benefits anyway), I’ll use the other forms of views here:
Let’s walk through this. First, we have the
current_user function-based view. This view will be used anytime the user revisits the site, reloads the page, or does anything else that causes React to forget its state. React will check if the user has a token stored in the browser, and if a token is found, it’ll make a request to this view. Since we’ve set things up properly, the token will be parsed automatically to check for authentication, and if validated we’ll receive the user object associated with that token in the request’s
user property. We can then serialize the user object, and return the data from the serializer in the response. This whole function then serves as the input for the
@api_view decorator, which specifies the request methods this view will respond to (in this case, just GET requests).
Next, we have our class-based
UserList view. When a request is routed to this view, a
UserSerializerWithToken serializer object is instantiated with the data the user entered into the signup form. The serializer checks whether or not the data is valid, and if it is, it’ll save the new user and return that user’s data in the response (including the token, since we’re using this particular serializer). Note that we specify the permissions for this class to be
permissions.AllowAny, because otherwise, the user would have to be logged in before they could sign up, which could be frustrating.
We have our views now, but as of yet still no way of accessing them. To do that, we need to assign them some routes. It’s customary to give each app its own route configuration, so create a new
core/urls.py file and edit it like so:
Now we’ll hook up this file to the root urls conf by editing that file (
With that, we’re almost good to go — but we still have a problem. Currently, when a user logs in, they receive their token but not any of their user data. To remedy this, we could make a separate request to the
current_user() view we defined earlier… but that’s annoying. Why make multiple requests? Instead, let’s customize our JWT settings a bit.
What we’re going to need to do is define a custom JWT response payload handler which includes the user’s serialized data. Within the
mysite directory, make a new file called
utils.py and fill it with the following:
All this is doing is adding a new ‘user’ field with the user’s serialized data when a token is generated. This is going to be our new default JWT response handler, which we can set up by adding a little bit to our
Now, when a user logs in, they’ll get all their user data along with their token... And we should finally be done! On to the frontend.
Setting Up React
We’ll be instantiating our React app using create-react-app, so go ahead and get that if you don’t have it already. Then run:
Making sure to call it whatever you please, of course. Change into the directory you just created. I’m mostly going to try to avoid using any additional packages unrelated to the matter at hand, but the one I will use is the
prop-types package to make sure everything is well documented. So let’s go ahead and install that:
npm i prop-types -S
components directory within
src. We’re going to fill this with the basic, essential components we’ll need to get our simple auth app up and running, starting with our navbar (really just an unordered list here). Create a new
Nav.js file within
components and edit it like so:
Basically, there will be two versions of the navbar, one for when the user is logged in and one for when they’re not. When logged out, clicking the links in the navbar will call a function which displays the relevant form (for logging in or signing up). All of this is dependent on props which will be passed in by the parent element.
Next let’s define some forms for logging in and signing up. Create two new files within
These files are virtually identical — the only reason I kept them separate is because normally you’d want to collect more info on a user when they sign up than you’d need when they log in (in fact, you can customize the signup form if you like… Django’s
User model supports
last_name fields in addition to
password. Be sure to add the relevant fields to the serializer, too, if you want the data back). Both components are stateful to keep track of the data in their controlled inputs, and both have
handle_change() methods to adjust the component’s state when the user types something into the form. Also, they both receive a prop which dictates how the form should be processed upon submission.
But where are these props coming from? Well, since this is a very simple example, I just put all the logic in the
App component. Open up your
App.js file and make it look like this:
This where the magic happens, so let’s parse it one bit at a time. We start with the
constructor(), where the component’s state is initialized and the
logged_in property is determined based on whether or not a token can be found in local storage. We then move on to the
componentDidMount() lifecycle method, where if a token has been found, we make a request to the
current_user() view we defined in Django. Notice that we specify the
Authorization header in the format ‘JWT <token>’. Each request to the API which requires the user to be authenticated will need to include this header, in this format, in order for the request to be processed. Then, we parse the response as JSON, and add the user’s username to the component’s state.
handle_login() method, we make a POST request to the
obtain_jwt_token view that we tried out before… only this time, because we changed the default response payload handler, the response from this viewpoint will include the user’s serialized data along with the token. Similarly, the
handle_signup() method makes a POST request to our
UserList view, which also returns the user’s serialized data and token. In both of these cases, the token is stored into local storage once the response has been parsed into JSON. Finally, we have the
handle_logout() method, which simply deletes the token from local storage (no request necessary).
And that’s about it! Everything else, in the
render() methods, just handles the UI. Run
npm start (and maybe reboot your Django server if you haven’t already), and you should see a simple but functional app with authentication capabilities.
One last thing: if you’ve been following along and you notice that your finished product looks different than mine, that’s because I changed my
App.css file to this:
This is obviously a very simple example —Normally, I’d incorporate a lot more error handling and form validations, as well as use Redux thunks to make the requests to the API. But since none of that is necessary to understand how to incorporate JWT authentication, so I opted to omit all of that here. One additional point: JWT encoding/decoding involves the use of a secret key, which defaults to the
SECRET_KEY constant defined in
settings.py. If you’re deploying an app that uses JWT to production, be sure to change this, or at least hide the secret key from Github.
I certainly learned a lot in writing this, and I hope you did too!