Daniel Roy Greenfeld

Daniel Roy Greenfeld

About | Books

Authenticating via JWT using Django, Axios, and Vue

VueJS Logo

Getting Django Rest Framework, JWT, Axios, and Vue.js to play nice isn't easy. Here's my quick-and-dirty cheatsheet that I wrote while glueing the pieces together.

Note: My architecture doesn't use django-webpack-loader. Instead, I'm running Django and Vue.js as two separate projects. I do this because I much prefer generating new projects with vue create over configuring webpack.

The Back-End

First, install some Django parts using the installer of your choice:

pip install Django
pip install djangorestframework
pip install django-cors-headers
pip install djangorestframework-jwt

Then, configure Django in settings.py:

INSTALLED_APPS = (
    ...
    'rest_framework',
    'rest_framework.authtoken',
    'corsheaders',
)

MIDDLEWARE_CLASSES = (
  ...
  'corsheaders.middleware.CorsMiddleware',
)

CORS_ORIGIN_ALLOW_ALL = False
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
    # TODO - set this properly for production
    'https://127.0.0.1:8080',
    'https://127.0.0.1:8000',
)

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        # By default we set everything to admin,
        #   then open endpoints on a case-by-case basis
        'rest_framework.permissions.IsAdminUser',
    ),
    'TEST_REQUEST_RENDERER_CLASSES': (
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer'
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 20,
}

from datetime import timedelta

JWT_AUTH = {
    'JWT_ALLOW_REFRESH': True,
    'JWT_EXPIRATION_DELTA': timedelta(hours=1),
    'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=7),
}

Once that's done, it's time to do modify the urls.py:

from django.conf.urls import include, url
...

urlpatterns = [
    ...
    # JWT auth
    url(r'^api/v1/auth/obtain_token/', obtain_jwt_token),
    url(r'^api/v1/auth/refresh_token/', refresh_jwt_token),
    # The rest of the endpoints
    url(r'^api/v1/', include('project.api', namespace='apiv1')),
    ...
]

Run the tests and fix them as they fail.

The Front-End

First off, add this to your Vuex store:

import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";

Vue.use(Vuex);

// Make Axios play nice with Django CSRF
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.xsrfHeaderName = "X-CSRFToken";

export default new Vuex.Store({
  state: {
    authUser: {},
    isAuthenticated: false,
    jwt: localStorage.getItem("token"),
    endpoints: {
      // TODO: Remove hardcoding of dev endpoints
      obtainJWT: "https://127.0.0.1:8000/api/v1/auth/obtain_token/",
      refreshJWT: "https://127.0.0.1:8000/api/v1/auth/refresh_token/",
      baseUrl: "https://127.0.0.1:8000/api/v1/",
    },
  },

  mutations: {
    setAuthUser(state, { authUser, isAuthenticated }) {
      Vue.set(state, "authUser", authUser);
      Vue.set(state, "isAuthenticated", isAuthenticated);
    },
    updateToken(state, newToken) {
      // TODO: For security purposes, take localStorage out of the project.
      localStorage.setItem("token", newToken);
      state.jwt = newToken;
    },
    removeToken(state) {
      // TODO: For security purposes, take localStorage out of the project.
      localStorage.removeItem("token");
      state.jwt = null;
    },
  },
});

Then, in a Vue component called something like components/Login.vue:

<template lang="html">
  <form class="login form">
    <div class="field">
      <label for="id_username">Username</label>
      <input
        v-model="username"
        type="text"
        placeholder="Username"
        autofocus="autofocus"
        maxlength="150"
        id="id_username"
      />
    </div>
    <div class="field">
      <label for="id_password">Password</label>
      <input
        v-model="password"
        type="password"
        placeholder="Password"
        id="id_password"
      />
    </div>
    <button @click.prevent="authenticate" class="button primary" type="submit">
      Log In
    </button>
  </form>
</template>

<script>
  import axios from "axios";

  export default {
    data() {
      return {
        username: "",
        password: "",
      };
    },
    methods: {
      authenticate() {
        const payload = {
          username: this.username,
          password: this.password,
        };
        axios
          .post(this.$store.state.endpoints.obtainJWT, payload)
          .then((response) => {
            this.$store.commit("updateToken", response.data.token);
            // get and set auth user
            const base = {
              baseURL: this.$store.state.endpoints.baseUrl,
              headers: {
                // Set your Authorization to 'JWT', not Bearer!!!
                Authorization: `JWT ${this.$store.state.jwt}`,
                "Content-Type": "application/json",
              },
              xhrFields: {
                withCredentials: true,
              },
            };
            // Even though the authentication returned a user object that can be
            // decoded, we fetch it again. This way we aren't super dependant on
            // JWT and can plug in something else.
            const axiosInstance = axios.create(base);
            axiosInstance({
              url: "/user/",
              method: "get",
              params: {},
            }).then((response) => {
              this.$store.commit("setAuthUser", {
                authUser: response.data,
                isAuthenticated: true,
              });
              this.$router.push({ name: "Home" });
            });
          })
          .catch((error) => {
            console.log(error);
            console.debug(error);
            console.dir(error);
          });
      },
    },
  };
</script>

<style lang="css"></style>

Summary

There you have it, my quick-and-dirty notes on getting Django REST Framework, JWT, Axios, and Vue.js to play nice together. Be aware there are a two significant problems:

  1. I'm not happy about using local storage, especially with JWT. In fact, my good friend Randall Degges has written about the problems of JWT.
  2. I don't cover logging out. ;)

Credits:


Tags: python django django-rest-framework Vue.js javascript