(Nuxt.js/Vue.js) Setting axios auth token in Vuex store resets after refresh

Karolis Stakėnas picture Karolis Stakėnas · Jan 23, 2018 · Viewed 7.8k times · Source

I have the following store code to handle login, logout, to fetch the user and to set the token to all axios requests as auth header.

It works great with client side render, say I go to login page, login, get the token, store it in cookies.. But when I refresh the page, it seems that no token is being set anymore.. I'm calling the fetch action on NuxtServerInit, still no luck.. Any ideas where my code is failing?

Heres my store/index.js file :

https://jsfiddle.net/3dc07yv4/

import Cookie from 'cookie'
import Cookies from 'js-cookie'

export const state = () => ({
  sidebar: true,
  token: null,
  user: null
})

export const mutations = {
  // SET SIDEBAR
  toggleSidebar (state) {
    state.sidebar = !state.sidebar
  },
  // SET USER
  setUser (state, user) {
    state.user = user
  },
  // SET TOKEN
  setToken (state, token) {
    state.token = token
  }
}

export const getters = {
  loggedIn (state) {
    return Boolean(state.user && state.token)
  }
}

export const actions = {
  async nuxtServerInit ({ dispatch }, { req }) {
    await dispatch('fetch')
  },
  // Update token
  async updateToken ({ commit }, token) {
    // Update token in store's state
    commit('setToken', token)
    // Set Authorization token for all axios requests
    console.log('Setting axios token to: ', token)
    this.$axios.setToken(token, '')
    // Update cookies
    if (process.browser) {
      // ...Browser
      if (token) {
        Cookies.set('ccmsToken', token, { expires: 1 })
      } else {
        Cookies.remove('ccmsToken')
      }
    } else {
      // ...Server
      let params = {
        domain: '/'
      }
      if (!token) {
        let expires
        let date = new Date()
        expires = date.setDate(date.getDate() + 1)
        params.expires = new Date(expires)
      }
      this.app.context.res.setHeader('Set-Cookie', Cookie.serialize('ccmsToken', token, params))
    }
  },

  // Fetch Token
  async fetchToken ({ dispatch }) {
    let token
    // Try to extract token from cookies
    if (!token) {
      const cookieStr = process.browser ? document.cookie : this.app.context.req.headers.cookie
      const cookies = Cookie.parse(cookieStr || '') || {}
      token = cookies['ccmsToken']
    }
    if (token) {
      await dispatch('updateToken', token)
    }
  },

  // Reset
  async reset ({ dispatch, commit }) {
    commit('setUser', null)
    await dispatch('updateToken', null)
  },

  // Fetch
  async fetch ({ getters, state, commit, dispatch }, username = 'admin', { endpoint = 'http://localhost:8000/api/user' } = {}) {
    // Fetch and update latest token
    await dispatch('fetchToken')
    // Skip if there is no token set
    if (!state.token) {
      return
    }

    // Try to get user profile
    try {
      const data = await this.$axios.$get(endpoint + '?username=' + username)
      commit('setUser', data)
    } catch (e) {
      // Reset store
      await dispatch('reset')
    }
  },

  // Login
  async login ({ dispatch }, { fields, endpoint = 'http://localhost:8000/api/login' } = {}) {
    try {
      // Send credentials to API
      let data = await this.$axios.$post(endpoint, fields)
      if (data.success) {
        await dispatch('updateToken', data['token'])
        // Fetch authenticated user
        await dispatch('fetch', data.user.username)
      } else {
        throw new Error(data.message)
      }
    } catch (error) {
      if (error.response && error.response.status === 401) {
        throw new Error('Bad credentials')
      }
      throw error
    }
  },

  // Logout
  async logout ({ dispatch, state }) {
    try {
      await dispatch('reset')
    } catch (e) {
      console.error('Error while logging out', e)
    }
  }

}

Answer

Karolis Stakėnas picture Karolis Stakėnas · Apr 16, 2018

I have solved this problem by implementing an interceptor that would inject the token into headers on every axios request. It looks like this:

export default ({ $axios, store }) => {
  $axios.defaults.baseURL = 'https://api.com/api/'

  if (process.server) {
    return
  }

  $axios.interceptors.request.use(request => {
    request.baseURL = 'https://api.com/api/'

    // Get token from auth.js store
    const token = store.state.token

    // Update token axios header
    if (token) {
      request.headers.common['Authorization'] = token
    }
    return request
  })
}

You use it as a nuxt plugin.