Access Vuex before Vue route is resolved

Viewed 6230

What do I have:

  1. router with auth-required routes and public routes
  2. vuex with user's auth state

What do I want: send request to server using axios to check user's auth state before app is loaded (before router is resolved).

router.js:

import Vue from 'vue'
import Router from 'vue-router'
import store from './store'

Vue.use(Router)

const router = new Router({
  ...
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/account',
      name: 'account',
      component: () => import(/* webpackChunkName: "account" */ './views/Account.vue'),
      meta: {
        requiresAuth: true
      }
    }
  ]
})

router.beforeEach((to, from, next) => {
  if (to.matched.some(route => route.meta.requiresAuth)) {
    if (store.state.authStatus)
      next()
    else
      next({name: 'home'})
  } else {
    next()
  }
})

export default router

store.js:

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    authStatus: false
  },
  mutations: {
    setAuthStatus(state, authStatus) {
      state.authStatus = authStatus
    }
  }
})

 axios.get(...)
   .then(response => {
     store.commit('setAuthStatus', true)
   })

export default store

main.js:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

My problem: when I enter mydomain.com/acount in browser (app is not loaded before) while being authorized, anyway I got redirect to home. And after redirect I see that I'm authorized (I set up some DOM element inside Home component which is shown only for authorized users).


I've tried this, didn't helped:

store.js:

const store = new Vuex.Store({
  ...
  actions: {
    setAuthStatus({commit}) {
      axios.get(...)
        .then(response => {
          commit('setAuthStatus', true)
        })
    }
  }
})

main.js:

store.dispatch('setAuthStatus').then(() => {
  new Vue({
    router,
    store,
    render: h => h(App)
  }).$mount('#app')
})

Edit: in main.js I tried to go from

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

to

new Vue({
  store,
  router,
  render: h => h(App)
}).$mount('#app')

and it also didn't helped.

3 Answers

Going around Vanojx1's answer, I solved my question next way.

store.js:

const store = new Vuex.Store({
  state: {
    authStatus: axios.get(...).then(response => {
      store.commit('setAuthStatus', true)
    }),
    userAuth: false
  },
  mutations: {
    setAuthStatus(state, authStatus) {
      state.userAuth = authStatus
    }
  }
})

router.js:

router.beforeEach((to, from, next) => {
  if (to.matched.some(route => route.meta.requiresAuth)) {
    store.state.authStatus.then(() => {
      //we're getting 200 status code response here, so user is authorized
      //be sure that API you're consuming return correct status code when user is authorized
      next()
    }).catch(() => {
      //we're getting anything but not 200 status code response here, so user is not authorized
      next({name: 'home'})
    })
  } else {
    next()
  }
})

In the navigation guard you need something async so save your axios promise in the store as authStatus. When it resolves commit and set the loggedIn status. In the navigation guard wait for the promise to be resolved and then call the next function to enter the next router.

Store.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    /*  EXAMPLE
    authStatus: new Promise(resolve => {
      setTimeout(() => {
        const requestResult = true;
        store.commit("setAuthStatus", requestResult);
        resolve(requestResult);
      }, 1000);
    }),
    */
    authStatus: axios.get(...).then((requestResult) => {
        store.commit("setAuthStatus", requestResult);
    }),
    loggedIn: false
  },
  mutations: {
    setAuthStatus(state, loggedIn) {
      state.loggedIn = loggedIn;
    }
  }
});

export default store;

router.js

router.beforeEach((to, from, next) => {
  if (to.matched.some(route => route.meta.requiresAuth)) {
    store.state.authStatus.then(loggedIn => {
      if (loggedIn) next();
      else next({ name: "home" });
    });
  } else {
    next();
  }
});

Check this solution working here

The problem is probably due to the thing that when you enter the url https://example.com/account

It starts the app and axios performs an asynchronous request to the server. So maybe you should try making an axios call from within the beforeEach.

By this way you'll have no problem authenticating the user.

The thing is that you cannot make axios synchronous so you have to return a promise with axios in order to achieve the functionality.

Please tell if this came of any help to you.

Related