I've encountered an issue in my VueJS SPA where a strange limbo state occurs. The application fails to recognize that the JWT token has expired, leading it to still display as if the user is logged in. This typically happens after periods of hibernation.
Despite this confusion, users are able to continue sending requests to the API, only to receive a 401
response indicating their unauthorized status.
I'm looking for a way to implement a universal handler for 401
responses. Essentially, I want to automatically clear all user-related data from Vuex and present the page as if the user were a guest, prompting them with a login form popup, etc. Otherwise, I'd be stuck writing a 401 handler for every single request.
I've tried using response interceptors with axios, and while they function properly, they lack access to Vuex (or Vue).
Whenever I attempt to bring Vuex or Vue into my Axios setup, I run into circular dependencies which ultimately break everything.
Even if I simply throw/return the error, I would still need to handle it separately for each request. So, my question is: How can I dispatch methods on this.$store
from within an axios interceptor?
The Axios file includes an export default class API
that gets globally added to Vue in main.js
:
import api from 'Api/api'
// ...
Vue.prototype.$http = api
I had previously assumed there must be a way to access Vue
through $http
, given that it's a global instance method. However, it seems my assumption was mistaken?
Code
main.js
// ...
import api from 'Api/api'
// ...
Vue.prototype.$http = api
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App },
vuetify: new Vuetify(opts),
});
api.js
import Client from './ApiClient'
const apiClient = new Client({ basePath: process.env.VUE_APP_API_URL })
const api = {
get(url) {
return apiClient._get(`${basePath}/${url}`)
},
post(url, data) {
return apiClient._post(`${basePath}/${url}`, data)
},
// ...
}
export default api
ApiClient.js
const axios = require('axios')
const errorHandler = (error) => {
if (error.response.status === 401) {
store.dispatch('user/logout') // here is the problem
}
return Promise.reject({ ...error })
}
export default class API {
constructor(options) {
this.options = Object.assign({ basePath: '' }, options)
this.axios = axios.create({ timeout: 60000 })
this.axios.interceptors.response.use(
response => response,
error => errorHandler(error)
)
}
// ...
}
Attempting to import the store in ApiClient.js
creates a dependency cycle, possibly due to importing Vue within it?
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import PersistedState from 'vuex-persistedstate'
import CreateMutationsSharer from 'vuex-shared-mutations';
import SecureLS from 'secure-ls';
// import modules
Vue.use(Vuex);
const ls = new SecureLS({ encodingType: 'aes' });
export default new Vuex.Store({
// options
})