Search code examples
vue.jslocal-storagepersistencevuexnuxt.js

Middleware executing before Vuex Store restore from localstorage


In nuxtjs project, I created an auth middleware to protect page. and using vuex-persistedstate (also tried vuex-persist and nuxt-vuex-persist) to persist vuex store.

Everything is working fine when navigating from page to page, but when i refresh page or directly land to protected route, it redirect me to login page.

localStorage plugin

import createPersistedState from 'vuex-persistedstate'

export default ({ store }) => {
  createPersistedState({
      key: 'store-key'
  })(store)
}

auth middleware

export default function ({ req, store, redirect, route }) {
    const userIsLoggedIn = !!store.state.auth.user
    if (!userIsLoggedIn) {
        return redirect(`/auth/login?redirect=${route.fullPath}`)
    }
    return Promise.resolve()
}

Solution

  • With the Current approach, we will always fail.

    Actual Problem is Vuex Store can never be sync with server side Vuex store.

    The fact is we only need data string to be sync with client and server (token).

    We can achieve this synchronization with Cookies. because cookies automatically pass to every request from browser. So we don't need to set to any request. Either you just hit the URL from browser address bar or through navigation.

    I recommend using module 'cookie-universal-nuxt' for set and remove of cookies.

    For Setting cookie after login

    this.$cookies.set('token', 'Bearer '+response.tokens.access_token, { path: '/', maxAge: 60 * 60 * 12 })
    

    For Removing cookie on logout

    this.$cookies.remove('token')
    

    Please go through the docs for better understanding.

    Also I'm using @nuxt/http module for api request.

    Now nuxt has a function called nuxtServerInit() in vuex store index file. You should use it to retrieve the token from request and set to http module headers.

    async nuxtServerInit ({dispatch, commit}, {app, $http, req}) {
        return new Promise((resolve, reject) => {
            let token = app.$cookies.get('token')
            if(!!token) {
              $http.setToken(token, 'Bearer')
            }
            return resolve(true)
        })
      },
    

    Below is my nuxt page level middleware

    export default function ({app, req, store, redirect, route, context }) {
        if(process.server) {
    
    
            let token = app.$cookies.get('token')
    
            if(!token) {
                return redirect({path: '/auth/login', query: {redirect: route.fullPath, message: 'Token Not Provided'}})
            } else if(!isTokenValid(token.slice(7))) { // slice(7) used to trim Bearer(space)
                return redirect({path: '/auth/login', query: {redirect: route.fullPath, message: 'Token Expired'}})
            } 
            return Promise.resolve()
            
        }
        else {
            const userIsLoggedIn = !!store.state.auth.user
            if (!userIsLoggedIn) {
                return redirect({path: '/auth/login', query: {redirect: route.fullPath}})
                // return redirect(`/auth/login?redirect=${route.fullPath}`)
            } else if (!isTokenValid(store.state.auth.tokens.access_token)) {
                return redirect({path: '/auth/login', query: {redirect: route.fullPath, message: 'Token Expired'}})
                // return redirect(`/auth/login?redirect=${route.fullPath}&message=Token Expired`)
            } else if (isTokenValid(store.state.auth.tokens.refresh_token)) {
                return redirect(`/auth/refresh`)
            } else if (store.state.auth.user.role !== 'admin')
                return redirect(`/403?message=Not having sufficient permission`)
            return Promise.resolve()
        }
    }
    

    I have write different condition for with different source of token, as in code. On Server Process i'm getting token from cookies and on client getting token store. (Here we can also get from cookies)

    After this you may get Some hydration issue because of store data binding in layout. To overcome this issue use <no-ssr></no-ssr> wrapping for such type of template code.