Originally published at www.melvinkoh.me.
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)
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.
npm install --save vue-axios axios vuex jwt-decode
In your main.js, add the following:
// main.jsimport 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.
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.
// 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 the actions to obtain JWT and refresh depends on how you design your Vue.
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 orig_iat.
As mentioned above, orig_iat 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 THE CONDITION WHERE A TOKEN IS EXPIRED AS WELL } } }
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.
JWT Authentication is now ready. You should tailor your Vue with the code provided to create a seamless UX.
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.
Your clap will definitely drive me further. Give me a clap if you like this post.
Update at 20 March 2018: An error in the snippet of inspectToken() is rectified. Thank Axel Violon for pointing it out.