JWT Authentication in Vue.js and Django Rest Framework - Part 2

·

0 min read

In Part 1, we set up our bac-kend with djangorestframework_jwt. Now we will set up our front-end to work with our DRF. (We will be using Vue.js, the solution is similar if you are to use it in React.js)

Set up Vue.js

Make sure you include the following modules:

  • axios -> Promise based HTTP client
  • Vue-Axios -> Binder of axios to Vue.$http
  • jwt_decode -> Decode JWT payload to get expiry timestamp and original issue at timestamp
  • Vuex (optional) -> State management (Redux in React.js)

We will be using axios instead of vue-resource as HTTP client, and assume all HTTP requests are handled by axios.

Install the requirements

npm install --save vue-axios axios vuex jwt-decode

Import and Bind to Vue

In your main.js, add the following:

// main.js
import axios from 'axios'
import VueAxios from 'vue-axios'
import jwt_decode from 'jwt-decode'
import Vuex from 'vuex'

Vue.use(Vuex);
Vue.use(VueAxios, axios);

// WE WILL ADD THE CODE LATER

// YOUR VUE INSTANCE

In my case, I define all my methods in Vuex Store actions. You can of course define it in your components script tag with slight modification.

Define State and Mutations in Vuex Store

I assume you understand how Vuex works. Take a look at its official documentation if you are not clear about how it works.

// main.js

const store = new Vuex.Store({
  state: {
    jwt: localStorage.getItem('t'),
    endpoints: {
      obtainJWT: 'http://0.0.0.0:10000/auth/obtain_token',
      refreshJWT: 'http://0.0.0.0:10000/auth/refresh_token'
    }
  },
  mutations: {
    updateToken(state, newToken){
      localStorage.setItem('t', newToken);
      state.jwt = newToken;
    },
    removeToken(state){
      localStorage.removeItem('t');
      state.jwt = null;
    }
  },
  actions: {
    // WE WILL ADD THIS LATER
  }
  })

From the code above, we create a state to store JWT and endpoints as single source of truth and mutations to manipulate the state of JWT.

You can see that I use localStorage to store our token as well as in state store. This is advisable since Vuex Store will be reinitialize if user refresh the page. You will want to keep your token stored persistently all the time.

Define Actions in Vuex Store

// main.js
// Add into the placeholder in previous code segment.

  actions:{
    obtainToken(username, password){
      const payload = {
        username: username,
        password: password
      }

      axios.post(this.state.endpoints.obtainJWT, payload)
        .then((response)=>{
            this.commit('updateToken', response.data.token);
          })
        .catch((error)=>{
            console.log(error);
          })
    },
    refreshToken(){
      const payload = {
        token: this.state.jwt
      }

      axios.post(this.state.endpoints.refreshJWT, payload)
        .then((response)=>{
            this.commit('updateToken', response.data.token)
          })
        .catch((error)=>{
            console.log(error)
          })
    }
    inspectToken(){
      // WE WILL ADD THIS LATER
    }
  }

Dispatch Vuex actions

Dispatch the actions to obtain JWT and refresh depends on how you design your Vue.

Inspect JWT Expire timestamp

We would like to inspect our JWT from time to time and to refresh it before it expires. To decode, we use jwt_decode to inspect the exp and _origiat.

As mentioned above, _origiat is the issuance timestamp of the first token in the token chain. We have set the maximum lifespan to 7 days in our server (Refer to Part 1).

In the code below, we will check the token against these conditions:

  1. IF it is expiring in 30 minutes (1800 second) AND it is not reaching its lifespan (7 days - 30 mins = 630000-1800 = 628200) => REFRESH
  2. IF it is expiring in 30 minutes AND it is reaching its lifespan => DO NOT REFRESH
  3. IF it has expired => DO NOT REFRESH / PROMPT TO RE-OBTAIN TOKEN (How? Perhaps invoke user to re-login)
// Add the following into inspectToken() action above

    inspectToken(){
      const token = this.state.jwt;
      if(token){
        const decoded = jwt_decode(token);
        const exp = decoded.exp
        const orig_iat = decode.orig_iat

        if(exp - (Date.now()/1000) < 1800 && (Date.now()/1000) - orig_iat < 628200){
          this.dispatch('refreshToken')
        } else if (exp - (Date.now()/1000) < 1800){
          // DO NOTHING, DO NOT REFRESH          
        } else {
          // PROMPT USER TO RE-LOGIN
          // THIS ELSE CLAUSE COVERS THEN CONDITION WHERE A TOKEN IS EXPIRED
        }
      }
    }

It is up to you to design how you would prompt user to re-login, it is frustrating if your page prompts users to login when they are not triggering any HTTP request. I suggest your page only prompts user when they invoke an action the requires authenticated HTTP request.

Last Words

JWT Authentication is now ready. You should tailor your Vue with the code provided to create a seamless UX.

Bonus: When to Inspect Token?

I always check the expiration of token whenever the main component is updated or mounted. I include a call to dispatch 'inspectToken' in the Vue instance lifecycle hook. Perhaps this is not the best solution, please let me know if you have better idea.

First published on 2017-12-08

Republished on Hackernoon