Making React and Django play well together — the “single page app” model

Aymeric Augustin
Jun 14, 2018 · 6 min read

Also available at, with syntax highlighting.

This message continues my analysis of the trade-offs involved in choosing an architecture for integrating React with Django.

I’m focusing on the alternative between two models:

  • The “single page app” model: a standalone JavaScript frontend makes API requests to a backend running on another domain;
  • The “hybrid app” model: the same backend serves HTML pages embedding JavaScript components and API requests.

After the “hybrid app” model, here’s how to implement the “single page app” model.

Like last time, we’ll call the app todolist.

Disclaimer: I’m starting with default project templates and making minimal changes in order to focus on the integration between the frontend and the backend.

As a consequence, I’m ignoring many best practices for Django and React projects. Keep in mind that I’m describing only one piece of the puzzle and that many variants are possible!

Why build a “single page app”?

In the “single page app” architecture, the frontend and the backend are maintained and deployed separately.

This architecture provides several benefits:

  • It’s well known and easy to grasp by developers who specialize in either frontend or backend technologies.
  • It provides more flexibility for choosing the best processes and tools. They may diverge between the frontend and the backend.
  • It reduces coupling: for example. Deploying a new version of the frontend can’t break the backend.
  • It encourages good practices like providing compatibility across API versions.

It’s an obvious choice for large teams maintaining complex products. They need a lot of coordination to build and integrate features already. They can absorb the overhead of managing the frontend and the backend separately.

Even for smaller projects, the cost of adopting this architecture is low enough that it can be a good choice.


Teams building single page apps usually create separate repositories for maintaining the frontend and the backend.

Let’s initialize Django and React applications in backend and frontend repositories.¹


Start a shell, go to the root of the backend repository and bootstrap the backend:

Start the development server:

Open http://localhost:8000/ in a browser to confirm that everything is working.


Start another shell, go to the root of the frontend repository and bootstrap the frontend:

Start the development server:

http://localhost:3000/ opens automatically in a browser.


The “single page app” model provides great dev / prod parity. I don’t even need separate “Production setup” and “Development setup” sections!

The frontend serves initial HTML and static assets. The backend serves API requests. They don’t communicate with one another.

You can deploy the frontend and the backend to production according to best practices for your preferred hosting platform.

You get all the features of the development environments without any additional integration effort.


You should use the same configuration across environments, except for settings involving these URLs, which you should substitute consistently.


Since the frontend and the backend run on separate domains, you must set up CORS, else API requests will fail.

In development, the backend must include the following HTTP header in responses:

and in production:

Furthermore, if you’re relying on cookies for authentication², CSRF protection³, or any other purpose, the backend must also include:

To achieve this, let’s install and configure django-cors-headers:


Django’s CSRF protection checks the Referer header of HTTPS requests to prevent CSRF attacks between subdomains of the same domain or between HTTP and HTTPS.

This creates an issue in our scenario. We’re planning to make requests across domains; they will fail the CSRF check.

Fortunately Django provides a setting to allow cross-domain requests from our frontend:⁴

Making an API request

Let’s ensure that our configuration works.

The Django documentation suggests two ways to obtain the CSRF token in order to include it in AJAX requests. Unfortunately they aren’t applicable to our setup:

  • The frontend running at http://localhost:3000/ cannot read the value of the CSRF cookie for the backend at http://localhost:8000/.⁵
  • The HTML isn’t generated by Django so there’s no way to inject the cookie in the DOM.⁶

Instead we’re going to get the CSRF token from a dedicated API endpoint.⁷

Create the following views in the backend:

Wire the new views in the URLconf:

Now let’s build a quick test in the frontend. In the example below:

  • getCsrfToken gets a CSRF token from the csrf view and caches it.
  • testRequest makes an AJAX request to the ping view. If it’s a POST request, then testRequest adds the CSRF token in a X-CSRFToken header, as expected by Django.
  • App triggers a GET request and a POST request when it loads. If these requests succeed, App changes the test result from KO to OK.

Look at the application in the browser. It should have reloaded automatically and say:

Test GET request: OK
Test POST request: OK


Going further

In a real application, I would write a wrapper around fetch to handle this for all API requests.

The wrapper would detect when a request fails the CSRF check. In that case it would refresh the CSRF token and retry the request.

To identify CSRF failures unambiguously, the easiest solution is to point CSRF_FAILURE_VIEW to a custom view that returns a HTTP 403 with a specific payload.

Perhaps you’re resisting the urge to ask…

What about JWT?

If I had chosen to rely on JWTs for authenticating users instead of cookies, I could have skipped all the CSRF-related settings.⁸

Indeed, JWTs are managed at the application level. JWTs are only sent by the browser to the server when the application code decides to do so.

This is unlike cookies which are managed by the browser. Cookies are sent implicitly with HTTP requests, providing the ambient authority that enables CSRF attacks.

Either way, you’ll need a small wrapper around fetch to inject a JWT or a CSRF token in AJAX requests. You’ll have to ensure that it behaves properly regardless of whether the user is logged in or logged out. Managing CSRF tokens adds a little bit of complexity but not much compared to handling authentication correctly.⁹

The bigger difference when switching authentication to JWTs is that the application becomes responsible for managing the storage, expiry and renewal of authentication credentials without introducing any vulnerability. The browser took care of that behind the scenes with cookies.

Even though there are off-the-shelf solutions for managing JWTs in browsers, I’m more comfortable with trusting browser vendors to get this right. They figured out many security issues over the past 25 years. They’ve become good at pushing security updates to users.

Another notable difference is that secure, http-only cookies have slightly better security properties than JWTs stored in localStorage or sessionStorage.

An attacker who manages a XSS attack can trivially exfiltrate a JWT stored in localStorage or sessionStorage and use it to impersonate the user, even if the victim closes their browser or logs out. In contrast, if authentication relies on cookies, exploiting a XSS attack takes more work and may required continued access to the compromised browser.

In a world where social engineering whoever has access to Google Tag Manager to add a compromised tag is a viable vector for mounting a XSS attack¹⁰, gaining a bit of defense in depth may be worth the effort.

Originally published at

Fractal Ideas

We boost your technical team’s ability to meet present and…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store