SSR + Vuex + JWT
See original GitHub issueI’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:
- Created 5 years ago
- Comments:7 (4 by maintainers)
Top GitHub Comments
So please close issue. 🙂
Thanks a lot Sir, I eneded up with this based on your repo.
/src/api/axios
src/entry-server.js
src/entry-client.js
Works fine. Thanks again