question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

I’m having some hard time getting this to work, and I’d love to see how you guys would do this.

Currently I’m using Vuejs + Vuex + Axios for async HTTP calls. Everything I’ve done is based on this template, but I don’t see any info on how to to the following.

My Vuex actions look like this:

src/store/actions/dashboard/properties.js

import { fetchUserProperties, fetchUserProperty } from '@/src/api/dashboard/properties'

let FETCH_USER_PROPERTIES = ({commit, state}, data) => {

	return fetchUserProperties(data)
	.then((result) => {
		console.log("FETCH_USER_PROPERTIES",result)
		commit('SET_USER_PROPERTIES', result)
	})
	.catch((error) => {
		console.log("error", error)
	})
}

src/api/dashboard/properties

import axios from '@/src/api/axios'

let fetchUserProperties = () => {

	return new Promise((resolve, reject) => {

		axios.get(`/profile/properties`)
		.then((result) => {
			resolve(result.data)
		})
		.catch((error) => {
			reject(error)
		})
	
	});
}

src/api/axios

import axios from 'axios';
import config from '@/config/config'
import https from 'https'

var instance = axios.create(args);

export let initializeApi = (store, router) => {

	// Set base URL
	instance.defaults.baseURL = config.API_URL

  instance.interceptors.response.use(responseSuccess, (error) => {
    if (error.response.status === 401) {

      store.commit('REMOVE_USER_AND_TOKEN')

      router.push('/login')
    }

    return Promise.reject(error)
  })
  
  // Auth
	instance.interceptors.request.use((requestConfig) => {  
  
    let token = store.state.auth.token
   	if (token) {
      	requestConfig.headers.Authorization = `Bearer ${token}`
  	}
  	
  	return requestConfig
  });  

}

export let updateToken = (token) => {
  instance.interceptors.request.use((requestConfig) => {  
  
    if (token) {
        requestConfig.headers.Authorization = `Bearer ${token}`
    }
    
    return requestConfig
  });
}

export default instance;

How does this work? Basically, both in entry-server.js and entry-client.js I make a call to this initializeApi function with value of the store.

entry-client.js

import Vue from 'vue'
import { app, router, store } from './client-create-app'
import { initializeApi } from '@/src/api/axios'

// Initialize api
initializeApi(store, router)


// wait until router has resolved all async before hooks
// and async components...
router.onReady(() => {

  // Add router hook for handling asyncData.
  // Doing it after initial route is resolved so that we don't double-fetch
  // the data that we already have. Using router.beforeResolve() so that all
  // async components are resolved.
  router.beforeResolve((to, from, next) => {
    const matched = router.getMatchedComponents(to)
    const prevMatched = router.getMatchedComponents(from)
    let diffed = false
    const activated = matched.filter((c, i) => {
      return diffed || (diffed = (prevMatched[i] !== c))
    })
    const asyncDataHooks = activated.map(c => c.asyncData).filter(_ => _)
    if (!asyncDataHooks.length) {
      return next()
    }

    bar.start()
    
    Promise.all(asyncDataHooks.map(hook => hook({ store, route: to, router })))
      .then(() => {
        bar.finish()
        next()
      })
      .catch(next)
  })

  // actually mount to DOM
  app.$mount('#app')
})

// service worker
if ('https:' === location.protocol && navigator.serviceWorker) {
  navigator.serviceWorker.register('/service-worker.js')
}

entry-server.js

import { createApp } from './app'
import { initializeApi } from '@/src/api/axios'

const isDev = process.env.NODE_ENV !== 'production'

// This exported function will be called by `bundleRenderer`.
// This is where we perform data-prefetching to determine the
// state of our application before actually rendering it.
// Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance.
export default context => {
  return new Promise((resolve, reject) => {
    const s = isDev && Date.now()
    const { app, router, store } = createApp()

    const { url } = context
    const route = router.resolve(url).route
    const { fullPath } = route

    if (fullPath !== url) {
      return reject({ url: fullPath })
    }

    if(context.cookies.token) {
      store.state.auth.token = context.cookies.token
    }

    if(context.cookies.user) {
      store.state.user = JSON.parse(decodeURIComponent(context.cookies.user))
    }

    // Initialize API
    initializeApi(store, router)
    // initializeApiV2(store, router)

    // set router's location
    if (route.matched[0].meta.requiresAuth && !context.cookies.token) {
      router.replace('/login')
    }else{
      // set router's location
      router.push(url)  
    }



    // wait until router has resolved possible async hooks
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents() 

      // no matched routes
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      // Call fetchData hooks on components matched by the route.
      // A preFetch hook dispatches a store action and returns a Promise,
      // which is resolved when the action is complete and store state has been
      // updated.
      Promise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({
        store,
        route: router.currentRoute,
        router: router
      }))).then(() => {
        isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
        // After all preFetch hooks are resolved, our store is now
        // filled with the state needed to render the app.
        // Expose the state on the render context, and let the request handler
        // inline the state in the HTML response. This allows the client-side
        // store to pick-up the server-side state without having to duplicate
        // the initial data fetching on the client.
        context.state = store.state
        context.meta = app.$meta()
        resolve(app)
      }).catch(reject)
    }, reject)
  })
}

So I thought this looks and works beautifully. Now all by Axios request get the Authorization header appended and everything is good.

However after hours of debugging I found this:

https://medium.com/@iaincollins/how-not-to-create-a-singleton-in-node-js-bd7fde5361f5

Which means that on the server (NodeJS), my /src/api/axios.js file is getting cached by node. So that when I require the axios instance in my API files, sometimes it returns an axios instance created by another user, along with the old user interceptors and token. Very bad.

How are you guys handling this case where all HTTP request from the app need to have an Authorization header attached?

I’d love to see how @yyx990803 would do this. Would be nice to have it added to this example too.

Please let me know if you need any more information.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
JounQincommented, Mar 28, 2018

So please close issue. 🙂

0reactions
BorjaRafolscommented, Mar 28, 2018

Thanks a lot Sir, I eneded up with this based on your repo.

/src/api/axios

import axios from 'axios';
import config from '@/config/config'
import https from 'https'

const createInstance = (store) => {

	let token = store.state.auth.token

	let instance = axios.create({
		'baseURL': config.API_URL,
		'headers': {
			'Authorization': `Bearer ${token}`
		}
	})

	instance.interceptors.response.use(
		(success) => {
			return success
		}, 
		(error) => {
	    	if (error.response.status === 401) {
				store.commit('REMOVE_USER_AND_TOKEN')
				router.push('/login')
	    	}
    		return Promise.reject(error)
  		})

	return instance
}

export { createInstance }

src/entry-server.js

import { createApp } from './app'
import { createInstance } from '@/src/api/axios'

const isDev = process.env.NODE_ENV !== 'production'

// This exported function will be called by `bundleRenderer`.
// This is where we perform data-prefetching to determine the
// state of our application before actually rendering it.
// Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance.
export default context => {
  return new Promise((resolve, reject) => {
    const s = isDev && Date.now()
    const { app, router, store } = createApp()

    const { url } = context
    const route = router.resolve(url).route
    const { fullPath } = route

    if (fullPath !== url) {
      return reject({ url: fullPath })
    }

    const token = context.cookies.token
    if(token) {
      store.state.auth.token = token
    }

    if(context.cookies.user) {
      store.state.user = JSON.parse(decodeURIComponent(context.cookies.user))
    }

    store.commit("SET_AXIOS", createInstance(store))

    /* Initialize API
    let instance = initializeApi(store, router)
    store.state.$axios = instance
    context.$axios = instance
    */
    // initializeApiV2(store, router)

    // set router's location
    if (route.matched[0].meta.requiresAuth && !context.cookies.token) {
      router.replace('/login')
    }else{
      // set router's location
      router.push(url)  
    }



    // wait until router has resolved possible async hooks
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents() 

      // no matched routes
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      // Call fetchData hooks on components matched by the route.
      // A preFetch hook dispatches a store action and returns a Promise,
      // which is resolved when the action is complete and store state has been
      // updated.
      Promise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({
        store,
        route: router.currentRoute,
        router: router
      }))).then(() => {
        isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
        // After all preFetch hooks are resolved, our store is now
        // filled with the state needed to render the app.
        // Expose the state on the render context, and let the request handler
        // inline the state in the HTML response. This allows the client-side
        // store to pick-up the server-side state without having to duplicate
        // the initial data fetching on the client.
        context.state = store.state
        context.meta = app.$meta()
        resolve(app)
      }).catch(reject)
    }, reject)
  })
}

src/entry-client.js

import Vue from 'vue'
import 'es6-promise/auto'
import { app, router, store } from './client-create-app'
import { createInstance } from '@/src/api/axios'
import ProgressBar from './components/ProgressBar.vue'

// global progress bar
const bar = Vue.prototype.$bar = new Vue(ProgressBar).$mount()
document.body.appendChild(bar.$el)

// a global mixin that calls `asyncData` when a route component's params change
Vue.mixin({
  beforeRouteUpdate (to, from, next) {
    const { asyncData } = this.$options
    console.log("beforeRouteUpdate", router)
    if (asyncData) {
      asyncData({
        store: this.$store,
        route: to,
        router: router
      }).then(next).catch(next)
    } else {
      next()
    }
  }
})

// Initialize api
store.commit("SET_AXIOS", createInstance(store))

// wait until router has resolved all async before hooks
// and async components...
router.onReady(() => {

  // Add router hook for handling asyncData.
  // Doing it after initial route is resolved so that we don't double-fetch
  // the data that we already have. Using router.beforeResolve() so that all
  // async components are resolved.
  router.beforeResolve((to, from, next) => {
    const matched = router.getMatchedComponents(to)
    const prevMatched = router.getMatchedComponents(from)
    let diffed = false
    const activated = matched.filter((c, i) => {
      return diffed || (diffed = (prevMatched[i] !== c))
    })
    const asyncDataHooks = activated.map(c => c.asyncData).filter(_ => _)
    if (!asyncDataHooks.length) {
      return next()
    }

    bar.start()
    
    Promise.all(asyncDataHooks.map(hook => hook({ store, route: to, router })))
      .then(() => {
        bar.finish()
        next()
      })
      .catch(next)
  })

  // actually mount to DOM
  app.$mount('#app')
})

// service worker
if ('https:' === location.protocol && navigator.serviceWorker) {
  navigator.serviceWorker.register('/service-worker.js')
}

Works fine. Thanks again

Read more comments on GitHub >

github_iconTop Results From Across the Web

Vue.js + Vuex - JWT Authentication Tutorial & Example
Tutorial on how to implement JWT Login Authentication with Vue.js and Vuex, including a working demo login page.
Read more >
[ISSUE] Vuex + Axios + JWT Token Auth #2680 - nuxt/nuxt.js
It seems, that it works fine with SSR, works fine with only Client-Side. Where it fails is to set the client token when...
Read more >
JWT Authentication in Vue/Nuxt — The Right Way - Medium
To make authenticated API calls from both the server and browser (client), we need to ensure that the tokens are accessible from both...
Read more >
How to make nuxt auth working with JWT - a definitive guide
Nuxt.js is a nice framework for creating both SSR and SPA apps easily in vue. It is easy to use, but sometimes there...
Read more >
node.js - How to store JWT Token in browser cache in a NUXT ...
I am using AXIOS, VUEX, JWT for this. however i am not able to set the store and set JWT token properly in...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found