Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement auto refresh in client side(vue.js)?

Note: I have seperated my client(Vue.js) and server(DjangoRest). I'm using JWT to validate every request made from the client to the server. Flow- Client sends user credentials to server. Server sends back a refresh and access token if credentials are valid. Client stores the access and refresh token. I have set the refresh token expiry to 1 week,access to 30 mins. Next, I want to make sure that the access token is auto refreshed 15 mins prior to its expiry. To do this, the stored refresh token in client side is send to the server, the server then issues a new access token and refresh token, sends it back to the client. How do i implement this in the Vuex store?. I'm a complete newbie to web development and vue.js. It would be great if someone could provide some code or explain in details.

I have already implemented loginUser,logout user,registerUser in store and they are working fine. But I'm stuck with the auto refresh logic. My guess is that the client has to repeatedly check the access token expiry time left. When about 15 mins is left, we have to initialize the autorefresh function. Please help me with this logic.

Here's my Vueex store:

import Vue from 'vue'
import Vuex from 'vuex'
import axiosBase from './api/axios-base'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
     accessToken: '' || null,
     refreshToken: '' || null
  },
  getters: {
    loggedIn (state) {
      return state.accessToken != null
    }
  },
  mutations: {
    loginUser (state) {
      state.accessToken = localStorage.getItem('access_token')
      state.refreshToken = localStorage.getItem('refresh_token')
    },
    destroyToken (state) {
      state.accessToken = null
      state.refreshToken = null
    }
  },
  actions: {
    registerUser (context, data) {
      return new Promise((resolve, reject) => {
        this.axios.post('/register', {
          name: data.name,
          email: data.email,
          username: data.username,
          password: data.password,
          confirm: data.confirm
        })
          .then(response => {
            resolve(response)
          })
          .catch(error => {
            reject(error)
          })
      })
    },
    // fetch data from api whenever required.
    backendAPI (context, data) {

    },
    logoutUser (context) {
      if (context.getters.loggedIn) {
        return new Promise((resolve, reject) => {
          axiosBase.post('/api/token/logout/')
            .then(response => {
              localStorage.removeItem('access_token')
              localStorage.removeItem('refresh_token')
              context.commit('destroyToken')
            })
            .catch(error => {
              context.commit('destroyToken')
              resolve()
            })
        })
      }
    },
    autoRefresh (context, credentials) {

    },
    loginUser (context, credentials) {
      return new Promise((resolve, reject) => {
        axiosBase.post('/api/token/', {
          username: credentials.username,
          password: credentials.password
        })
          .then(response => {
            localStorage.setItem('access_token', response.data.access)
            localStorage.setItem('refresh_token', response.data.refresh)
            context.commit('loginUser')
            resolve(response)
          })
          .catch(error => {
            console.log(error)
            reject(error)
          })
      })
    }
  }
})

Thank you in advance.

like image 367
nishant_boro Avatar asked Nov 29 '22 21:11

nishant_boro


1 Answers

This is very much an idea question as you've pointed out and as such, there are many ways of solving it.

One thing I try to keep in mind when dealing with such mechanisms is to always avoid polling when possible. Here's a solution inspired by that design principle.

JWT tokens are valid for a very specific amount of time. The time left for expiration is readily available as part of the access token. You can use a library such as jwt-decode to decode the access token and extract the expiration time. Once you have the expiration time, you have a several options available:

  • Check token every time before making a request to know if it needs to be refreshed
  • Use setTimeout to refresh it periodically X seconds before it expires

Your code could be implemented as follows:
Note: Please treat the following as pseudo-code. I have not tested it for errors---syntax or otherwise.

export default new Vuex.Store({
  ...
  actions: {
    refreshTokens (context, credentials) {
      // Do whatever you need to do to exchange refresh token for access token
      ...
      // Finally, call autoRefresh to set up the new timeout
      dispatch('autoRefresh', credentials)
    },
    autoRefresh (context, credentials) {
      const { state, commit, dispatch } = context
      const { accessToken } = state
      const { exp } = jwt_decode(accessToken)
      const now = Date.now() / 1000 // exp is represented in seconds since epoch
      let timeUntilRefresh = exp - now
      timeUntilRefresh -= (15 * 60) // Refresh 15 minutes before it expires
      const refreshTask = setTimeout(() => dispatch('refreshTokens', credentials), timeUntilRefresh * 1000)
      commit('refreshTask', refreshTask) // In case you want to cancel this task on logout
    }
  }
})
like image 52
Guru Prasad Avatar answered Dec 09 '22 09:12

Guru Prasad