Firebase onAuthStateChanged interfers createUserWithEmailAndPassword.then - javascript

I am using
firebase.auth().createUserWithEmailAndPassword(email, password1)
.then(() => {
var user_uid = firebase.auth().currentUser.uid;
db.collection(ll_collection).doc(user_uid).set({
user_review: 0,
number_of_reviews: 0
})
})
It creates a document named after the user's id right after the account creation.
I also have another function on the same file that checks if an user is logged in or not:
firebase.auth().onAuthStateChanged(function(user) {
if(user) {
setTimeout(function() {
window.location.href = "/";
}, 400)
}
})
I had to set a timeout on this function, since the account is logged in right after the account creation. If I don't put the timeout, it is going to redirect to "/" right after the account creation without creating the document. It makes sense, since onAuthStateChanged is in realtime.
But is there another way I can redirect the logged in user on the page without interfering with the operation after the account creation?
Thanks in advance.

I typically only use onAuthStateChanged() and not the then() completion handler of the user creation.
So in that case both document creation and redirect would be in the onAuthStateChanged and you can synchronize there:
firebase.auth().createUserWithEmailAndPassword(email, password1);
firebase.auth().onAuthStateChanged(function(user) {
if(user) {
db.collection(ll_collection).doc(user.uid).set({
user_review: 0,
number_of_reviews: 0
}).then(function() {
window.location.href = "/";
});
}
})
If you only want to create the document when the user is new, you can check if they already have a document, or reject the document (re)creation in security rules.

Related

Firebase: use updated profile with onAuthStateChanged()

I have a little twisted problem with my React app. I'm using Firebase for authentication. What I need to do is to register a user and set their displayName immediately. Entire problem could be avoided if Firebase allowed to call createUserWithEmailAndPassword() without signing user in, but it doesn't.
I do this:
const submitHandler = async event => {
event.preventDefault();
const auth = await firebase.auth();
const { user } = await auth.createUserWithEmailAndPassword(email, password);
await user.updateProfile({
displayName: userName
});
};
The problem is that I'm using firebase.auth().onAuthStateChanged() in my root component to handle current user state, so it's called as soon as createUserWithEmailAndPassword() is done, before the profile is updated. I'm not sure how to tell onAuthStateChanged() about the updated displayName.
I managed to create a working solution, but I'm not happy with it. I tricked onAuthStateChanged() to ignore users without displayName like so:
useEffect(() => {
const unsubscribeAuth = firebase.auth().onAuthStateChanged((user) => {
if (user === null || user.displayName !== null) {
setLoggedInUser(user);
}
});
return () => unsubscribeAuth();
}, []);
And in my sign up component, I do this:
const submitHandler = async event => {
event.preventDefault();
const auth = await firebase.auth();
const { user } = await auth.createUserWithEmailAndPassword(email, password);
await user.updateProfile({
displayName: userName
});
await auth.signOut();
await auth.updateCurrentUser(user);
};
That's obviously hackish and slower than necessary, because I'm signing in twice. It works as intended though, ignoring the auto-login, then signing out (doing nothing, as the user state hasn't been updated before) and then signing in, triggering onAuthStateChanged() with the updated user.
How can I improve my app to sign up users without this double madness?
Updating the user profile not normally execute the onAuthStateChange listener, as (usually) the authentication state won't change because of this API call.
You can force a reload of the user's ID token to get their latest profile information.

Firebase - keep user signed in while localStorage object is valid

I have an app I'm building with Ionic, Angular2, and Firebase.
What I'm currently doing is that when a user log's in or registers, their auth.currentUser information is being saved in localStorage. My code is currently assuming that if a user has the user variable in localStorage set that the user is also signed in. However, I just realized that that isn't actually the case.
My localStorage variable user looks like this:
and I'm getting it like this:
ngOnInit() {
Promise.all([ //data storage get all..
this.storage.get('user')
])
.then(([user]) => {
this.userData = user; //set the user storage to this.userData
console.log(this.userData)
})
.catch(error => {
console.log("Storage Error.");
});
}
So I have all of the users information, except the fact that they aren't actually signed in... So when I do:
this.afAuth.auth.onAuthStateChanged((user) => {
if (user) {
console.log(user)
} else {
console.log("Not logged in")
}
});
It shows the user isn't logged in. Is it possible for me to keep the user logged in while I have the localStorage information?
Or maybe just have the login state always be logged in unless the user signs out? With it never expiring?
What should I do at this point? Any advice would be great! Thank you.
Firebase manger user in a different way. You should not depend on localstorage to detect if user is logged it. You should depend on firebase to check if user is logged in. Since firebase is built on web sockets, it should be really quick. So let rewrite the ngOnInit as follows:
ngOnInit() {
const user = this.afAuth.auth.currentUser;
if (user) {
this.userData = user;
} else {
// force them to reauthenticate
user.reauthenticateWithCredential(credential)
.then(function() {
// User re-authenticated.
this.userData = this.afAuth.auth.currentUser;
}).catch(function(error) {
// An error happened.
});
}
}
Read more about managing users in Firebase here: https://firebase.google.com/docs/auth/web/manage-users. Hope this helps.

Check if Firebase Facebook user exists without creating a user starting from anonymous user

In Firebase I need to check if a Facebook user exists without creating the user. Initially the user is anonymous, and they try to login with Facebook. I want this to fail if the Facebook account is not already linked to a user in my system. It won't be linked to the current user because they are anonymous,
If I use Auth.signInAndRetrieveDataWithCredential I expected a "auth/user-not-found" error, but instead the user is simply created. Is this a bug or expected?
let credential = firebase.auth.FacebookAuthProvider.credential(
event.authResponse.accessToken)
firebase.auth().signInAndRetrieveDataWithCredential(credential).then( (userCredential) => {
let user = userCredential.user
app.debug("DEBUG: Existing user signed in:"+user.uid)
this.loginSuccess(user)
}).catch( (err) => {
app.error("ERROR re-signing in:"+err.code)
$("#login_status_msg").text(err)
})
If I use User.reauthenticateAndRetrieveDataWithCredential instead I get the error "auth/user-mismatch" which makes sense because user is currently anonymous. However, I was expecting "auth/user-not-found" may be thrown instead if the credential doesn't exist, but that doesn't happen.
I don't see a way to take my anonymous user, have them login with Facebook and then see if another user is already linked to that Facebook credential without creating the user if it doesn't exist.
If you're wondering why? My scenario is:
The system allows anonymous users
A user logs in, then converts to a logged in user by registering with Facebook.
App uninstall
App reinstall
User starts up the app and is initially anonymous.
They try and login with Facebook again. At this point I want to stop them from creating a user if they don't have one already. If they have a user ID already, the code works fine and changes their anonymous account ID to the original user ID which is good.
I found a solution! It wasn't too hard to implement, but it does seem hacky.
So we know that when using signInAndRetrieveDataWithCredential(cred) for facebook login, the account is created even if it does not exist yet. To solve this, we need to make sure that we handle the following three things:
Detect if the account is new
Delete the current account that was created by firebase
Throw an error to get out of the current flow and return to wherever you were before.
I just implemented and tested this solution, and it seems to work great:
// ... do your stuff to do fb login, get credential, etc:
const userInfo = await firebase.auth().signInAndRetrieveDataWithCredential(credential)
// userInfo includes a property to check if the account is new:
const isNewUser = _.get(userInfo, 'additionalUserInfo.isNewUser', true)
// FIRST, delete the account we just made.
// SECOND, throw an error (or otherwise escape the current context.
if (isNewUser) {
firebase.auth().currentUser.delete()
throw new Error('Couldn\'t find an existing account.')
}
// If the user already exists, just handle normal login
return userInfo.user
The reason I did this was to ensure that users had to go through the "create account flow" in my app. Your case would be really easy to implement as well, something like the following:
let credential = firebase.auth.FacebookAuthProvider.credential(event.authResponse.accessToken)
firebase.auth().signInAndRetrieveDataWithCredential(credential)
.then(userCredential => {
const isNewUser = userCredential.additionalUserInfo.isNewUser
if (isNewUser) {
firebase.auth().currentUser.delete()
// The following error will be handled in your catch statement
throw new Error("Couldn't find an existing account.")
}
// Otherwise, handle login normally:
const user = userCredential.user
app.debug("DEBUG: Existing user signed in:"+user.uid)
this.loginSuccess(user)
}).catch( (err) => {
app.error("ERROR re-signing in:"+err.code)
$("#login_status_msg").text(err)
})
You can use linkAndRetrieveDataWithCredential:
let credential = firebase.auth.FacebookAuthProvider.credential(
event.authResponse.accessToken);
anonymousUser.linkAndRetrieveDataWithCredential(credential).then( (userCredential) => {
// Firebase Auth only allows linking a credential if it is not
// already linked to another account.
// Now the anonymous account is upgraded to a permanent Facebook account.
}).catch( (err) => {
// Check for code: auth/credential-already-in-use
// When this error is returned, it means the credential is already
// used by another account.
})
You can use the method fetchSignInMethodsForEmail to check if an specific email is already associated to an specific provider or not. Doing this you will be able to check if one if the SighInMethods of the email associated to your user contains Facebook.com or not.
I show you below an example about how I manage this cases on my application. I'm using an RxJavaWrapper on my code, but you will understand the point of how to manage it:
RxFirebaseAuth.fetchSignInMethodsForEmail(authInstance, email)
.flatMap { providerResult ->
if (!providerResult.signInMethods!!.contains(credential.provider)) {
return#flatMap Maybe.error<AuthResult>(ProviderNotLinkedException(credential.provider))
} else {
return#flatMap RxFirebaseAuth.signInWithCredential(authInstance, credential)
}
}
.subscribe({ authResult ->
//Manage success
}, { error ->
//Manage error
})
First I check the providers associated to the email of the user(You can retrieve it from the provider)
If the list of SignInMethods contains my credential provider, I throw an error, if not, I call my signInWithCredential method to create the user.
Continue your workflow.
What I did to solve this problem without relying on the call to linkAndRetrieveDataWithCredential to fail and using the catch block to sign in the already existing user is to save the userID field that getCurrentAccessToken returns.
const { userID } = data;
this.props.setFacebookId(userID); // saves the userID on the server
I can later check if this userID already exists next time the user signs up with facebook.

Authentication - JavaScript - Logout issue

I need some help with my problem.
firebase.auth().onAuthStateChanged(user => {
if(user) {
console.log('log in');
window.location.href ='event_list.html'
}});
btnLogout.addEventListener('click', e => {
firebase.auth().signOut().then(function() {
console.log("not log in");
window.location.href = 'index.html';
}, function(error) {
//
});;
});
Logging in works correctly but if I'm trying logout - user probably logout correctly - back to login/signup site but immediately I'm redirecting to "event_list.html" as still logged in user.
My sign in/sign up site is - index.html
I want logout and put other data - login/pass.
One other question: How can I get email variable from this script and use it in other JavaScript script?
I want do banner - Hi (mail of user actually log in), Welcome back and insert it to all my HTML sites.
I suggest you to take a look at this code in github.
About your example, I think that the problem is with onAuthStateChanged method (user has logged out, so the auth state has changed, so it's triggered...).
Try to use signInWithEmailAndPassword what is explained here.
By other way, separated question I would, first make a search in SO, and then, if no answered meets my needs, I would asked it in a new one!
I'm also new to Firebase but firebase.auth().signOut() should do the trick for you
And in-order to identify if the user is logged in you can use the onAuthStateChanged event. If the passed parameter is null which means user is logged out if you have a valid value then you can resolve the user from that
ex: (I have not tried this but this should work)
firebase.auth().onAuthStateChanged(user => {
if(user) {
console.log('User Logged in :' + user );
window.location.href ='event_list.html'
}else{
console.log('User Not Logged in');
window.location.href = 'index.html';
}
});
btnLogout.addEventListener('click', e => {
firebase.auth().signOut();
});

AngularFire: Resuming an anonymous session

From the Simple Web Login docs, it claims you can resume a session by doing the following:
var auth = new FirebaseSimpleLogin(chatRef, function(error, user) {
...
});
Is there an equivalent for AngularFire?
The $firebaseSimpleLogin constructor takes a firebase reference as its only parameter..
Specifically, what I'm trying to do is login once anonymously and resume that login on a full page refresh. Is that achievable?
Whether or not your auth session is preserved is determined by the parameters you pass into the login method. Whether you use FirebaseSimpleLogin or $firebaseSimpleLogin is irrelevant here.
var auth = $firebaseSimpleLogin(ref);
$rootScope.$on('$firebaseSimpleLogin:login', function(user) {
console.log('logged in', user.uid);
});
// automagically logs in the next time you call $firebaseSimpleLogin() after page refresh
auth.$login('anonymous', { rememberMe: true });
// does not automagically log in
auth.$login('anonymous');
UPDATE As of Angular 1.x and Firebase 2.x this syntax and behavior has changed.
From the remember attribute in Firebase docs:
If not specified - or set to default - sessions are persisted for as
long as you have configured in the Login & Auth tab of your App
Dashboard. To limit persistence to the lifetime of the current window,
set this to sessionOnly. A value of none will not persist
authentication data at all and will end authentication as soon as the
page is closed.
var auth = $firebaseAuth(ref);
$firebaseAuth.$onAuth(function(user) {
console.log('logged ' + (user? 'in' : 'out'), user && user.uid);
});
// stays logged in for length specified in the app dashboard
auth.$authAnonymously();
// stays logged in until the browser closes
auth.$authAnonymously({ remember: 'sessionOnly' });
// does not stay logged in on page refresh
auth.$authAnonymously({ remember: 'none' });

Categories