JWT Authentication in Django, Part 2: Implementing the Frontend
In the previous article, we walked through the creation of a JWT authentication system using the django-rest-framework
and dj-rest-auth
libraries. In this article, we’ll explore how to communicate with this backend from a decoupled frontend. Some prior knowledge is assumed of:
- A frontend framework like Vue or React
- The use of a client like @vue/cli or Nuxt for serving the frontend app
- State management tools (like Vuex or Redux)
- The Axios http client
To see the previous article, Part 1, click here.
Last time, we created a solid backend for JWT using Django. One might be forgiven for thinking that implementing a frontend to communicate with this should be trivial, especially with the myriad of frontend authentication libraries available on npm. However, I haven’t had great success with these libraries, so I prefer to just create my own custom login forms and API calls.
Really, the important thing when dealing with the frontend client is doing our due diligence to combat attacks like cross-site scripting (XSS) and cross-site request forgery (CSRF). The former is generally a greater danger than the latter when dealing with JWT. The first thing to think about is Cookies.
Cookies, and where they get stored
If you followed the last tutorial, you should hopefully be set up with a Django backend running SSL via django-sslserver
, and with local DNS designations via /etc/hosts
that allow you to visit the backend at an address like
https://api.example.com:8000
For the frontend, I usually use Quasar Framework or Nuxt. Like many frontend clients that include a development server, Quasar uses Webpack under the hood, and the webpack development server has a setting https
that you can set to true
. This should take care of the frontend SSL. Remember to add an s
to all your urls.
Now it’s time to add an endpoint somewhere within your frontend app that hits your backend . I’ll leave this to you – we’re aiming to create a login form that posts to the https://api.example.com:8000/login
endpoint, and maybe an authenticated route that should only be available to an authenticated user.
In your browser (by the way I personally advocate the Brave browser, all the devtools of Chrome, with none of the spying), open your frontend address, and enter your login details to the login form. This will send a POST
request to Django, which will return your tokens (one access token and one refresh token).
At this point, the magic happens.
If you open up the devtools of Google Chrome or Brave (other browsers will be slightly different) and head to Application => Cookies
, you may find nothing there. This confused me for a long time. As it turns out, the use of https and the custom DNS we implemented in Part 1 are now bearing fruit, and the cookies are indeed being saved – they just aren’t appearing. However, if you go to Network
and click the request made to /login
, there should now be a Cookies
tab alongside the Headers/Preview/Timing
tabs. If you then check the access headers going out with the requests, you should see the access-tokens being included.
Using state management to control frontend authentication
My preference is the use of state management to write the Javascript logic, but it’s not mandatory. From this point onward, my code snippets will pertain to Vuex, but if you’re using something else like React/Redux, the concepts should still be useful. I’ll also be using Axios, but you can probably accomplish the same thing with fetch()
.
The critical thing to set when initializing Axios is to set the flag withCredentials: true
. You can pass this parameter into any request, or set it in the defaults like so:
import Vue from 'vue'
import axios from 'axios'export default (state) => {
axios.defaults.withCredentials = true
axios.defaults.baseURL = process.env.API_URL
Vue.prototype.$axios = axios
state.$axios = axios}
The above code might be found in a boot file, or a plugin, or elsewhere; it depends on your frontend framework. Note how I have also added Axios to the state, so I can now use it anywhere.
Whenever withCredentials = true
, the browser will include the tokens in its headers. Here’s an example of a typical Axios request, placed within a Vuex module:
Parting thoughts
Whether you’re writing the frontend logic from scratch or using a library, hopefully this has helped in filling in some of the blanks to get your frontend playing nice with your JWT Django backend.
In terms of next steps, there’s plenty left to do. However you choose to implement the frontend, you should probably be covering the following points:
- Add frontend forms, buttons and http requests for each backend endpoint you need (e.g.
/login
,/logout
,/password-change
, etc). - Add a token refresh endpoint. It may also be a good idea to remove the access-token when logging in (you can do this by simply removing the
JWT_AUTH_COOKIE
entry insettings.py
) and storing only the refresh token. Then you can add the access-token to memory by hitting the/refresh-token
endpoint if the state changes. - Add redirects in your router logic to send the user to the login form if they are not authenticated.
Thanks for reading this article. If you have any questions or comments, feel free to drop me a line at johnckealy.dev@gmail.com.