Navigation guards are perfect for redirecting unauthorized users to a login page, but what does one do to redirect unauthorized vuex actions to a login page?
I can do this easily enough in the vue method where I'm calling the action like so:
if (!this.isLoggedIn) {
this.$router.push({ name: 'login', query: { comeBack: true } })
$(`.modal`).modal('hide')
return
}
But then I'm inserting these 5 lines of code for every component method that requires authorization.
All the solutions I can think of sound hacky, so I'm wondering what the vuex way is:
In order to reject it at the vuex action level, I have to pass up the $router instance, and I'm still reusing the 5 lines for each action that requires auth.
I can handle this in a utility file, but then I'm handling $router instance in that file.
I can use a global vue mixin and call it (a) before making a call and then again (b) when getting a 401 back from the server.
All those seem odd. What vuex way am I missing here?
This sounds like a job for middleware. Unfortunately, Vuex doesn't have an official way to do middleware.
There is a subscribeAction()
but that runs after the commit, so does not allow mods to the action. There is also a proposal Middleware processing between actions and mutation.
As I see it, we want middleware to be able to do two generic things
The second is difficult to do without patching store.dispatch()
or messing with the private property _actions
after store has been created.
However, to guard an action as you describe, we only need to be able to cancel it.
Here is a poor-man's middleware for the modules pattern for Vuex store which I prefer.
store construction from modules
export const store = new Vuex.Store({
modules: {
config,
pages: applyMiddleware(pages),
measures,
user,
loadStatus,
search
}
})
applyMiddleware
const applyMiddleware = function(module) {
if(module.middlewares) {
Object.values(module.middlewares).forEach(middlewareFn => {
Object.keys(module.actions).forEach(actionName => {
const actionFn = module.actions[actionName]
module.actions[actionName] = addMiddleware(actionName, actionFn, middlewareFn)
});
})
}
return module;
}
addMiddleware
const addMiddleware = function(actionName, actionFn, middlewareFn) {
return function(context, payload) {
const resultFn = middlewareFn(actionFn)
if(resultFn) {
resultFn(context, payload)
}
}
}
defining middleware in the module
const actions = {
myAction: (context, payload) => {
...
context.commit('THE_ACTION', payload)
...
},
}
const middlewares = {
checkAuthMiddleware: (action) => {
return this.isLoggedIn
? action // if logged-in run this action
: null; // otherwise cancel it
}
}
export default {
state,
getters,
mutations,
actions,
middlewares
}
This implementation has module-specific middleware functions, but you could also define them globally and apply to as many modules as applicable.