I have a bit of a conundrum in my Vue/vuex/vue-router + Firebase app. The bootstrap code is supposed to initialize Firebase, as well as the signed in user (if any) in Vuex:
new Vue({
el: '#app', router, store, template: '<App/>', components: { App },
beforeCreate() {
firebase.initializeApp(firebaseConfig)
// Because the code below is async, the app gets created before user is returned...
.auth().onAuthStateChanged(user => {
this.$store.commit('syncAuthUser', user) // this executes too late...
})
}
})
In my routes, I have
{ path: '/home', name: 'Home', component: Home, meta: { requiresAuth: true } }
as well as
router.beforeEach((to, from, next) => {
let authUser = store.getters.authUser // this is not set just yet...
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!authUser) {
next('signin') // /home will always hit here...
}
next()
}
else {
next()
}
})
As mentioned in the comments, the app gets the authenticated user too late. So, when you go to /home
while logged in, the app will run the beforeEach
check in the routes... And the user instance will not be available just yet, so it will think that you are not logged in, when in fact you are (it's clear from my computed properties that are properly updated a few moments later).
So, how do I change my code, so that the Vue app gets initialized only after Firebase returns the user and after that user is passed to the Vuex store? Plese let me know if I am doing something completely wrong.
onAuthStateChanged
would only be triggered once. So, if I put my crucial this.$store.commit('syncAuthUser', user)
there, then that code would only execute when the app is created, and not when the user logs out for example.onAuthStateChanged
in another Promise
, for sure there must be a better way; plus, it seems wrong to place the auth user initialization login in the routes file.Setting up Firebase Auth Here, click on Add new project and name the project vue-firebase-auth . You will be redirected to the project's dashboard. On the dashboard, click on the web icon to register your front-end app. Name your app vuex-firebase-authentication and click on Register app .
Vuefire is a small and pragmatic solution to create realtime bindings between a Firebase RTDB or a Firebase Cloud Firestore and your Vue application. Making it straightforward to always keep your local data in sync with remotes databases.
public class AuthState extends Object. Collects authorization state from authorization requests and responses. This facilitates the creation of subsequent requests based on this state, and allows for this state to be persisted easily.
The easiest way is to wait for the auth state before mounting your app:
const app = new Vue({
router,
store,
render: h => h(App),
});
firebase.auth().onAuthStateChanged(user => {
store.commit('authStateChanged', user);
app.$mount('#app');
});
If you want to show a spinner before mount put it in #app
:
<div id="app"><div class="loader">Loading...</div></div>
In fact it will make your actions cleaner and the app more reliable if you extract the auth state logic to a authStateChanged
commit as above, and handle your route redirection here too. As this will handle if the auth state changes while you are on a route while router.beforeEach
will check when you're going to one.
firebase.auth().onAuthStateChanged(user => {
store.commit('auth/authStateChanged', user);
const isAuthed = Boolean(user);
const { meta } = router.history.current;
if (isAuthed && meta.isPublicOnly) { // e.g. /login + /signup
router.push('/dashboard');
} else if (!isAuthed && meta.isPrivate) {
router.push('/login');
}
app.$mount('#app');
});
Note that the first router.beforeEach
callback happens before this, so you should ignore it until auth state has been determined. Since we handle the first redirection in onAuthStateChanged
it doesn't matter.
state: {
isInited: false,
isAuthed: false,
},
mutations: {
authStateChanged(state, user) {
state.isAuthed = Boolean(user);
state.isInited = true;
}
}
router.beforeEach((to, from, next) => {
const { isInited, isAuthed } = store.state.user;
if (isInited) {
if (!isAuthed && to.matched.some(record => record.meta.isPrivate)) {
return next('/login');
} else if (isAuthed && to.matched.some(record => record.meta.isPublicOnly)) {
return next('/dashboard');
}
}
next();
});
You could probably prevent the route loading at all until isInited
is true alternatively.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With