Pattern For Redirecting On Unauthorized Vuex Actions
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?
Answer
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
- cancel the action
- allow alternative actions to be called
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.
Related Questions
- → How to update data attribute on Ajax complete
- → October CMS - Radio Button Ajax Click Twice in a Row Causes Content to disappear
- → Octobercms Component Unique id (Twig & Javascript)
- → Passing a JS var from AJAX response to Twig
- → Laravel {!! Form::open() !!} doesn't work within AngularJS
- → DropzoneJS & Laravel - Output form validation errors
- → Import statement and Babel
- → Uncaught TypeError: Cannot read property '__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' of undefined
- → React-router: Passing props to children
- → ListView.DataSource looping data for React Native
- → Can't test submit handler in React component
- → React + Flux - How to avoid global variable
- → Webpack, React & Babel, not rendering DOM