Ad

Pattern For Redirecting On Unauthorized Vuex Actions

- 1 answer

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:

  1. 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.

  2. I can handle this in a utility file, but then I'm handling $router instance in that file.

  3. 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?

Ad

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.

Ad
source: stackoverflow.com
Ad