Loading firebase user identity before everything else?
-
Currently doing a SSR app with Quasar v1 CLI & Firebase for auth (via Vuex). I have Firebase loaded in boot. Auth flow (simplified): if logged in, user can access anything under “player” layout (multiple pages). If not logged in, user can only see /login. Redirection accomplished via Router.beforeEach nav guard in router’s index.js.
If just clicking links (router.push), everything works great. However, when reloading the page or trying to access an auth restricted page, it’ll bounce the logged-in user back to /login. From /login, clicking a nav link will work just fine again.
I think the problem is on reload/URL entry, the entire state/js is wiped clean. List below of the fixes I tried. Long story short, though, I noticed that Firebase’s initial onAuthStateChange requests were returning null until the page fully loaded, and then user identity would be properly loaded to the state & returned. No matter how early I tried to tuck the Firebase listener into the flow, I got the same results. I thought it was Firebase’s problem until I followed Tobias’ suggestions link here for persistent storage via localstorage / cookies, and while I could get them working, my store.state.user.user wouldn’t update until redirection to /login had already occurred. Before then, store.state.user.user returned null. After, it properly read from localStorage.
I’ve tried putting beforeCreated listeners on the relevant pages/layouts to get initial login state:
mainAuthLayout.vue
beforeCreate() { this.$auth.onAuthStateChanged(user => { if (user) { user.getIdToken().then(idToken => { this.$store.dispatch("user/setUser", idToken) this.$router.push("/home/main") }) } else { this.$store.dispatch("user/setUser", null) } }); }
Didn’t work. I tried putting onAuthStateChange / getUser methods in the firebase boot file
boot/firebase.js
import firebase from "firebase/app" import "firebase/auth" import config from "./env.json" export const fireApp = !firebase.apps.length ? firebase.initializeApp(config) : firebase.app() export const AUTH = fireApp.auth(); export default ({ store, Vue }) => { AUTH.onAuthStateChanged(user => { if (user) { user.getIdToken().then(idToken => { store.dispatch("user/setUser", idToken); }); } else { store.dispatch("user/setUser", null); } }); Vue.prototype.$auth = AUTH; }
That also didn’t work. I tried a pre-fetch on App.vue (basically the same code), which also didn’t work. In all of this the same problem: redirection to /login before store / $auth ever recognized that the user was already logged in.
Incidentally:
router/index.jsRouter.beforeEach((to, from, next) => { const currentUser = store.state.user.user const requiresAuth = to.matched.some(record => record.meta.requiresAuth) if (requiresAuth && !currentUser) next("/") else next() })
There’s no problem if I put my redirect logic as a .then(this.$router.push) in the pages themselves, but that is somewhat inelegant as it flashes the pages before redirection. Alternatively I could redirect users to a loading screen for a pretty short period of time which then routes them to the proper page once auth/store has had a chance to kick in, but I’d like to avoid if possible.
Any help or directional feedback would be so welcome. Even just suggestions of where I could be looking / reading more. I’ve thrown 6 hours at this already, happy to throw more. Just wanted to see if any of you knowledgeable folks would be able to quickly spot something I was missing.
-
@darshie The way I solved it is by initialising the state from local storage in the
Router.beforeEach(
mutation.js file is like
import { LocalStorage } from 'quasar'; var local = LocalStorage; export function init (s, p) { s.user = local.getItem('user'); s.hash = local.getItem('hash'); s.session_id = local.getItem('session_id'); // what ever else you need to read from local storage do it here }
My router/index.js file is like
import Vue from 'vue'; import VueRouter from 'vue-router'; import routes from './routes'; Vue.use(VueRouter); export default function ({ store, ssrContext }) { const router = new VueRouter({ scrollBehavior: () => ({ y: 0 }), routes, mode: process.env.VUE_ROUTER_MODE, base: process.env.VUE_ROUTER_BASE }); // initialise state store.commit('init'); router.beforeEach((to, from, next) => { // non public pages should only be visible to users logged in if (!to.meta.public && !store.state.user) { next({ path: '/', query: { redirect: to.fullPath } }); return; } next(); }); return router; }
Basically you look into local storage before you route the user.
-
I think this should work, but I’m getting the error
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): TypeError: local.getItem is not a function
…and I don’t have the foggiest how to address it. Is it possibly because I’m using SSR? I don’t get that issue when running in SPA mode, but then, I don’t get any issues when running in SPA mode–my persistedstate / firebase properly loads before the page, so I don’t get re-directions on refresh.
-
Okay… got it working with cookies–that’ll have to do for now.
-
@darshie the function is called slighly different in 0.17 my code is for v1 forgot to say
LocalStorage.get.item(key)
https://quasar-framework.org/components/web-storage.html#Getting-Started