I am using firebase authentication with vue application
Every time I restart the page after I log in a user currentUser turns to null
firebase.auth().signInWithEmailAndPassword(this.email, this.password)
.then(() => this.$router.push({name: 'Home'}))
.catch(err => this.feedback = err.message)
and in vue router
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
let user = firebase.auth().currentUser
if (!user) {
next({name: 'Login'})
} else next()
} else next()
})
I want the user to sign in once not every time the page restarts
This is caused because beforeEach is getting executed before firebase has fully finished initialization.
You can use onAuthStateChanged observer to make sure the vue app is initialized only after the firebase is fully initialized.
One way to fix it is to wrap the vue initialization code in main.js(new Vue( ... )) with onAuthStateChanged like this:
let app;
...
firebase.initializeApp( ... );
firebase.auth().onAuthStateChanged(() => {
if (!app) { // ignore reinitializing if already init (when signing out/login)
new Vue( ... )
...
}
})
Related
in a VueJS / QuasarJS application Im using firebase-js-sdk [1] together with firebaseui-web [2] to handle authentication.
After successful auth with any of the configured providers (e.g. password, google, apple, etc) I want to check which provider the user used. But immediately after successful authentication the user.providerData[] array that should contain the information is empty.
BUT if I reload my app the user.providerData[] array is suddenly populated correctly.
Iยดm checking for user data with something like this
import { getAuth } from "firebase/auth";
const auth = getAuth();
const user = auth.currentUser;
if (user) {
console.log(user.providerData)
}
After that the user object is fully populated (incl auth tokens, etc) but the user.providerData[] array is empty. Only after a page reload (CTRL-R) does the array get populated.
I searched both projects issues pages and documentation and didnt find anything that could explain this.
Im thankful for ANY idea where to look next!
EDIT
As suggested by #aside Im using onAuthStateChanged to check for updates of the user state.
onAuthStateChanged(
fbAuth,
(user) => {
if (user) {
console.log("onAuthStateChanged: user found");
console.log("onAuthStateChanged: user.providerData", user.providerData);
console.log("onAuthStateChanged: user", user);
} else {
console.log("onAuthStateChanged: no user found");
}
},
function (error) {
console.log("onAuthStateChanged:", error);
}
);
But even if I wait minutes after authentication is completed, still the user.providerData array is only populated after a page reload.
Here is a full demo: https://codesandbox.io/s/github/perelin/firebase-auth-providerdata-test
Thanks in advance :)
Im using
"firebase": "9.6.1",
"firebaseui": "6.0.0",
[1] https://github.com/firebase/firebase-js-sdk
[2] https://github.com/firebase/firebaseui-web
Your app should call getAuth().currentUser.reload() to refresh the local user data after login.
This could be done either in beforeRouteEnter() nav guard of the LoggedIn view:
// LoggedIn.vue
import { getAuth, signOut } from "firebase/auth";
export default {
async beforeRouteEnter(to, from, next) {
await getAuth().currentUser?.reload() ๐
next()
},
}
demo 1
Or in the onAuthStateChanged callback:
// main.js
onAuthStateChanged(
fbAuth,
async (user) => {
await user?.reload() ๐
},
)
demo 2
Your code is only running once instead of running every time the auth state is updated.
If you want to listen to any changes to the auth state, use a callback along with onAuthStateChanged as described here.
https://firebase.google.com/docs/auth/web/manage-users#get_the_currently_signed-in_user
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// Check used provider here
const providerData = user.providerData;
// ...
} else {
// User is signed out
// ...
}
});
The reason checking/requesting the user object right after authentication does not work is that it might take firebase a second to update the providerData array. signInWithX might therefore return before the property is updated.
If I have my createUserWithEmailAndPassword function structured like this
await auth.createUserWithEmailAndPassword(email, password).then((userCredential) => {
code stuff...
return promise
}).then(() => {
...
}).catch(() => {
...
}).finally(() => {
...
})
When in the promise chain would the onAuthStateChanged observer fire-up?
The onAuthStateChanged observer is triggered only when a user either signs in or signs out.
From the documentation:
Prior to 4.0.0, this triggered the observer when users were signed in, signed out, or when the user's ID token changed in situations such as token expiry or password change. After 4.0.0, the observer is only triggered on sign-in or sign-out.
To keep the old behavior, see firebase.auth.Auth.onIdTokenChanged
That being said if you have the onAuthStateChanged observer, it'll trigger as soon as the user logs in and the code inside of your .then() block may or may not completely execute. If your auth observer redirects users to some other page if logged in, then there is a high chance that your user will be redirected before your then block completes.
If you need to execute some code after the user logs in, you should unsubscribe from the auth observer:
const auth = firebase.auth()
const authObserver = auth.onAuthStateChanged((user) => {
if (user) {
// redirect
}
})
btnLogin.onclick = async function () {
//unsubscribe
authObserver()
await auth.createUserWithEmailAndPassword(email, password).then((userCredential) => {
// onAuthStateChanged will trigger here if not disabled
//code stuff...
//return promise
}).then(() => {
// your code
})
}
Background
I am using GoogleAuthProvider, with the default LOCAL persistence.
When I navigate to the page, I do:
firebase.initializeApp(firebaseConfig)
firebase.auth().currentUser // this is always null
firebase.auth().onAuthStateChanged(user => {
console.log("authStateChanged", user)
})
If the user is logged in, the callback is called once, with the user.
If the user is not logged in, the callback is also called once, with null.
This suggests I could wait until the first callback after navigating to the page to get the real login state before deciding what view to display, for instance. (I originally thought that it would not get called with null, and so I could end up waiting indefinitely)
Question
Would that be idiomatic usage? Does it seem like it will be robust against updates to firebase? Where can I find this discussed in the official documentation?
2022 Edit: in firebase web SDK 9, it's
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
export const isReady = new Promise(resolve => {
const unsubscribe = onAuthStateChanged(auth, (/* user */) => {
resolve(/* user */)
unsubscribe()
})
})
P.S: The reason I don't resolve with the user is because it is available at auth.currentUser, while the promise would retain an outdated value.
Looking at similar questions such as Pattern for Firebase onAuthStateChanged and Navigation Guards - Quasar app it seems this is indeed the way it's done.
So I have come up with the following to differentiate the initial condition:
export const isReady = new Promise(resolve => {
const unsubscribe = firebase.auth().onAuthStateChanged(() => {
resolve()
unsubscribe()
})
})
I export this Promise from the module where I wrap firebase, so I can begin other initialization while waiting for an authoritative authentication state.
this worked for me instead. NB: For those user Quasar
export default async ({ app, router, store }) => {
return new Promise(resolve => {
const unsubscribe = auth.onAuthStateChanged((user) => {
auth.authUser = user
resolve()
unsubscribe()
})
})
}
Nuxt.js is focuses on server side rendering and has an asyncData property that is called once before the page component is loaded.
I am trying something like:
async asyncData({params}) {
// firebase.auth().onAuthStateChanged((user)=>{ // <-- this doesn't work in the asyncData property
let user = await firebase.auth().currentUser
let info = {}
console.log(user)
user.uid === null // true
}
Two similar questions:
firebase.auth().currentUser is null
Get firebase.auth().currentUser with async/await
have solutions which do not seem to work with nuxt...
I have also tried:
function getCurrentUser(auth) {
let userLoaded = false;
return new Promise((resolve, reject) => {
if (userLoaded) {
resolve(firebase.auth().currentUser);
}
const unsubscribe = auth.onAuthStateChanged(user => {
userLoaded = true;
unsubscribe();
resolve(user);
}, reject);
});
}
It seems like onAuthStateChanged is triggered on client-side only. But the thing is, SSR functionality would make sense only for non-authenticated users, for authed-user scenario might as well just put the firebase call logic into mounted hook.
I'm currently using redux / redux-thunk to fetch a user using api-sauce like so
let authToken = await AsyncStorage.getItem('#TSQ:auth_token')
if (authToken) {
store.dispatch(fetchUser(authToken))
console.log('show login screen')
// dont worry, if the token is invalid, just send us to onboarding (api determines this)
loggedInView()
} else {
Onboarding ()
}
....
export const fetchUser = authToken => async dispatch => {
console.log('dispatching auth token')
console.log('here goes request')
let res = await api.get(`/auth/${authToken}`);
if (res.ok) {
console.log('have the user')
dispatch(
setUser(res.data)
)
} else {
dispatch({
type: 'SET_USER_DEFAULT'
})
}
}
When this code is ran, the user is still loading and the console.logs are not in order
`dispatching auth token`
`here goes request`
`show login screen`
Why is this happening?
This is because the actual call to store.dispatch(fetchUser(authToken)) is synchronous - the dispatch() method is not asynchronous, so the logging "show login screen" will occur immediately after execution of the fetchUser() method.
If you want loggedInView() to be executed after a response is returned from your network request (ie the call to the async method api.get()), then you could consider refactoring your code in the following way:
if (authToken) {
store.dispatch(fetchUser(authToken))
// Remove navigation from here
} else {
Onboarding ()
}
And then:
export const fetchUser = authToken => async dispatch => {
console.log('dispatching auth token')
console.log('here goes request')
let res = await api.get(`/auth/${authToken}`);
if (res.ok) {
console.log('have the user')
// Occurs after network request is complete
console.log('show login screen')
// Add navigation here to go to logged in view now that request is complete
loggedInView()
dispatch(
setUser(res.data)
)
} else {
dispatch({
type: 'SET_USER_DEFAULT'
})
}
Hope this helps!