SSR/PWA + Cookie + Auth
-
Hello,
I need some help to implemant a persistant auth session in a SSR/PWA quasar app. I’m stuck with hydratation errors from the server because the cookie is not available on server side.
I tried many things but i think i’m a little lost with all of these tests
I tried with a boot file wich persist the state with the cookie but i still get the errors. I followed the indications from this topic : https://forum.quasar-framework.org/topic/3306/how-to-make-vuex-store-persist/t
So far this is what i have done :
#router/index.js Router.beforeEach(async (to, from, next) => { const requiresAuth = to.matched.some(route => route.meta.requiresAuth) if (requiresAuth) { await store .dispatch('user/startSession') .then(() => { next() }) .catch(() => { next('/login') }) } else { next() } })
#user/actions.js export async function startSession ({ commit, dispatch, getters }) { return new Promise(function (resolve, reject) { let token = getters.getUserToken() if (token) { axios.defaults.headers.common['Authorization'] = 'Bearer ' + token commit(types.USER_TOKEN_CHANGED, { newToken: token }) dispatch('me') resolve() } else { reject() } }) }
I don’t know why it persist all my state object. Path option doens’t seems to work in this boot file :
#boot/persist-auth.js import { Cookies } from 'quasar' import createPersistedState from 'vuex-persistedstate' export default async ({ app, router, store, Vue, ssrContext }) => { const cookies = process.env.SERVER ? Cookies.parseSSR(ssrContext) : Cookies const options = { path: '/' } createPersistedState({ path: [ 'user', 'user.token', 'user.user.token', 'user.user', 'User.user', 'User.user.token' ], storage: { getItem: key => JSON.stringify(cookies.get(key)), setItem: (key, value) => cookies.set(key, value, options), removeItem: key => cookies.remove(key) } })(store) }
With this i get a cookie with all my state object stored in the browser. But when i refresh my browser on a page that need user to get auth, the server render the login page while the browser remains on the page i was (url stay the same).
Did i miss something about the cookies being server side rendered ?
Also if i use the q-nossr component on my pages where auth is necessary will it solve my hydratation problems ?I saw this issue on Git for adding the ssrContext in the modules. But i do not understand the part of getting the module aware of it. With this i will not need to persist my state and i could check the cookie in my actions, maybe it could correct my issues
https://github.com/quasarframework/quasar/issues/2285Thanks !
-
You may need to adjust the actions if you use modules as this way for a Vue/Vuex project with a single store.js file. But the way I did it is stick the below routerBackup code to the bottom of my router.js file(right to the bottom). And then add the subsequent code into my actions and mutations + states one by one.
It enabled me to reload the data in the app each time the browser is shut. I have also noticed the issue you describe where certain details are not updating on time when used in certain .js files, This is why I used the below method, no more issues. The actions are asynchronous so no worry. If anyone has a better way of doing it, you are welcome to post it.routerBackup.beforeEach((to, from, next) => { store.dispatch('resetErrors'); store.dispatch('fetchGlobalSettingsFromApi'); // Very important part let role = localStorage.getItem('rid'); let accessToken = localStorage.getItem('accessToken'); if (to.meta.requiresAuth) { if (!role || !accessToken) { routerBackup.push({path: '/login'}); } else { if (to.meta.adminAuth) { if (role === "admin") { return next(); } else { routerBackup.push({path: '/login'}); } } else if (to.meta.customerAuth) { if (role === "customer") { return next(); } else { routerBackup.push({path: '/customer'}); } } else if (to.meta.userAuth) { if (role === "user") { return next(); } else { routerBackup.push({path: '/user'}); } } } } else { return next(); } });
export default routerBackup;
In my store, my fetchGlobalSettingsFromApi actions look like this:
//FETCH GLOBAL SETTINGS
fetchGlobalSettingsFromApi(context) { if (localStorage.getItem('homepage_title') === null || localStorage.getItem('homepage_description') === null || localStorage.getItem('homepage_keywords') === null || localStorage.getItem('gdpr') === null || localStorage.getItem('newsletter') === null || localStorage.getItem('admin_email') === null) { api.get('/loading') .then(response => { let homepage_title = response.data.data[0].homepage_title; let homepage_description = response.data.data[0].homepage_description; let homepage_keywords = response.data.data[0].homepage_keywords; let gdpr = response.data.data[0].gdpr; let newsletter = response.data.data[0].newsletter; let admin_email = response.data.data[0].admin_email; context.commit('fetchGlobalSettingsFromApi', { homepage_title, homepage_description, homepage_keywords, gdpr, newsletter, admin_email }); }) .catch(error => { if (error.response.status < 500) { context.commit('apiErrorMessage', error); } else { context.commit('noServerError'); } }); } else { let homepage_title = localStorage.getItem('homepage_title'); let homepage_description = localStorage.getItem('homepage_description'); let homepage_keywords = localStorage.getItem('homepage_keywords'); let gdpr = localStorage.getItem('gdpr'); let newsletter = localStorage.getItem('newsletter'); let admin_email = localStorage.getItem('admin_email'); context.commit('fetchGlobalSettingsFromApi', { homepage_title, homepage_description, homepage_keywords, gdpr, newsletter, admin_email }); } }
Mutation:
fetchGlobalSettingsFromApi(state, data) { let homepage_title = data.homepage_title; let homepage_description = data.homepage_description; let homepage_keywords = data.homepage_keywords; let gdpr = data.gdpr; let newsletter = data.newsletter; let admin_email = data.admin_email; localStorage.setItem('homepage_title', homepage_title); localStorage.setItem('homepage_description', homepage_description); localStorage.setItem('homepage_keywords', homepage_keywords); localStorage.setItem('gdpr', gdpr); localStorage.setItem('newsletter', newsletter); localStorage.setItem('admin_email', admin_email); state.homepage_title = localStorage.getItem('homepage_title'); state.homepage_description = localStorage.getItem('homepage_description'); state.homepage_keywords = localStorage.getItem('homepage_keywords'); state.gdpr = localStorage.getItem('gdpr'); state.newsletter = localStorage.getItem('newsletter'); state.admin_email = localStorage.getItem('admin_email'); state.isLoggedIn = localStorage.getItem('isLoggedIn'); state.accessToken = localStorage.getItem('accessToken'); state.refreshToken = localStorage.getItem('refreshToken'); state.rid = localStorage.getItem('rid'); state.name = localStorage.getItem('name'); },
I scratch ed me head for days on this and finally used this method, and now I have a “settings” area in my admin account where I load all my settings which are sent to my laravel app. Each time the app loads a page it checks for the settings. I did not notice any slowdown and the app is really fast.
No idea if this is an ideal way of doing it but it worked great on my project.
-
Let me rephrase this… it does not load the settings each time or it would be a waste of resources, it only fetches via the api the settings if 1 of the setting is missing in the localstorage as shown here:
fetchGlobalSettingsFromApi(context) { if (localStorage.getItem('homepage_title') === null || localStorage.getItem('homepage_description') === null || localStorage.getItem('homepage_keywords') === null || localStorage.getItem('gdpr') === null || localStorage.getItem('newsletter') === null || localStorage.getItem('admin_email') === null) { api.get('/loading') .then(response => { let homepage_title = response.data.data[0].homepage_title; let homepage_description = response.data.data[0].homepage_description; let homepage_keywords = response.data.data[0].homepage_keywords; let gdpr = response.data.data[0].gdpr; let newsletter = response.data.data[0].newsletter; let admin_email = response.data.data[0].admin_email; context.commit('fetchGlobalSettingsFromApi', { homepage_title, homepage_description, homepage_keywords, gdpr, newsletter, admin_email }); }) .catch(error => { if (error.response.status < 500) { context.commit('apiErrorMessage', error); } else { context.commit('noServerError'); } }); } else { let homepage_title = localStorage.getItem('homepage_title'); let homepage_description = localStorage.getItem('homepage_description'); let homepage_keywords = localStorage.getItem('homepage_keywords'); let gdpr = localStorage.getItem('gdpr'); let newsletter = localStorage.getItem('newsletter'); let admin_email = localStorage.getItem('admin_email'); context.commit('fetchGlobalSettingsFromApi', { homepage_title, homepage_description, homepage_keywords, gdpr, newsletter, admin_email }); } }
-
This post is deleted!