onAuthStateChanged doesn't get called when email is verified in flutter - firebase

When a user signs up in my app he gets a verification e-mail. The onAuthStateChanged-listener gets called when a user gets created using createUserWithEmailAndPassword but not after the email got verified.
I have a separate class which handles all authentications. The following method is to sign up user
Future<FirebaseUser> signUpUser(email, password) async {
final FirebaseUser user = await _auth.createUserWithEmailAndPassword(email: email, password: password);
assert (user != null);
assert (await user.getIdToken() != null);
return user;
}
This method is called in my StatefulWidget using this method
void _signUpUser() async {
try {
await Auth().signUpUser(_email, _password)
..sendEmailVerification();
} catch (e) {
print(e.toString());
}
}
And onAuthStateChanged is set up in the initState method of my StatefulWidget
FirebaseAuth.instance.onAuthStateChanged.listen((user) {
print("Auth State Changed!");
if (user.isEmailVerified) {
print("EMail verified!");
}
}

onAuthStatechanged is triggered only in case of user Login or Logout & not on Email verification.
As Per Doc -
onAuthStatechanged Adds an observer for changes to the user's sign-in state.
The observer will be triggered in the following scenarios:
When auth().onAuthStateChanged() is first called. It will trigger with
the initial Auth state. If the user is returning from an
auth().signInWithRedirect() operation, the observer will wait for that
operation to resolve before initially triggering.
When a new user signs.
When an already signed in user signs out.

At the moment I'm providing a button to 'complete email validation'. This calls User.reload - so this appears to be enough per #Frank van Puffelen's comment above. It's much less satisfactory than getting a status update event; I may implement a loop to check the status for a period after sending an email so that the app passes through automatically.

You can simply do a firebase.auth().signOut after sending the email verification link. And when the user clicks on sign in again, it'll automatically fire onAuthStateChanged.

Use the where Function:
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
Stream<FirebaseUser> get onAuthStateChanged {
return _firebaseAuth.onAuthStateChanged.where((user)=> user.isEmailVerified);
}

Related

Flutter sign in with google lost authentication when the internet connection lost, how to re-auth it?

We are currently building an app that requires only google sign in and communicate with the Firebase depending on those data that we get with sign in.
The problem is this; after logging in to the app if the user lost internet connection and then re-establishes that connection without closing the app, FirebaseAuth.currentUser is not null but at the same time the user is not authenticated so the functions that i need to communicate with the DB is not working.
My question is simply this;
How do i check for the authentication and then re-authanticate the user.
My signin method is below, i tried to re-sign in every time when user enters some certain pages but that just not seemed good but does solves the problem. Is there a optimized way?
Future<String> signInWithGoogle() async {
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await _googleSignIn!.signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication? googleAuth =
await googleUser!.authentication;
// Create a new credential
final GoogleAuthCredential credential = (GoogleAuthProvider.credential(
accessToken: googleAuth!.accessToken,
idToken: googleAuth.idToken,
) as GoogleAuthCredential);
// Once signed in, return the UserCredential
final UserCredential? authResult =
await _auth!.signInWithCredential(credential);
final User? user = authResult!.user;
//assert(user!.isAnonymous);
assert(await user!.getIdToken() != null);
final User? currentUser = _auth!.currentUser;
assert(user!.uid == currentUser!.uid);
return 'signInWithGoogle succeeded: $user';
}
Edit:
We were using connectivity_plus plugin so we created a listener for it and if the user changes its connection to wifi or mobile we simply re-signin them. It worked for us it may have work for you as well;
#override
void initState() {
subscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
if (result == ConnectivityResult.mobile ||
result == ConnectivityResult.wifi ||
result == ConnectivityResult.ethernet) {
Login().signInWithGoogle();
}
print("${result}");
});
// TODO: implement initState
super.initState();
}
dispose() {
super.dispose();
subscription.cancel();
}
The Flutter Firebase docs have a re-authenticate user option and it sounds quite similar to your situation.
Re-authenticate a user

Firebase Auth authStateChanges trigger

I'm using firebase to authenticate a user and create a user in firestore database :
final auth.FirebaseAuth _firebaseAuth;
Future<void> signUp(
{#required String email, #required String password}) async {
assert(email != null && password != null);
try {
await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
await createUserInDatabaseIfNew();
} on Exception {
throw SignUpFailure();
}
}
With firebase, once the method .createUserWithEmailAndPassword() is executed, it triggers right after the authStateChanges which I am using in my code to send the new user in the user stream, and eventually retrieve its data from the database
Stream<User> get user {
return _firebaseAuth.authStateChanges().map((firebaseUser) {
return firebaseUser == null ? User.empty : firebaseUser.toUser;
});
}
StreamSubscription<User> _userSubscription = _authenticationRepository.user.listen((user) {
return add(AuthenticationUserChanged(user));}
if(event is AuthenticationUserChanged){
if(event.user != User.empty){
yield AuthenticationState.fetchingUser();
User userFromDatabase;
try{
var documentSnapshot = await _firebaseUserRepository.getUser(event.user.id);
userFromDatabase = User.fromEntity(UserEntity.fromSnapshot(documentSnapshot));
yield AuthenticationState.authenticated(userFromDatabase);
}
The problem that I am facing, is that because of _firebaseAuth.createUserWithEmailAndPassword, _firebaseAuth.authStateChanges is triggerd before the user is created in the database, and eventually when I try to retrieve that user, it still does not exist in the database.
I would like _firebaseAuth.authStateChanges() to be triggered after my method createUserInDatabaseIfNew runs.
How could I achieve that ?
I would like _firebaseAuth.authStateChanges() to be triggered after my method createUserInDatabaseIfNew runs.
The auth state change listener fires when the user's authentication state change, which is when their sign in completes. There's no way to change this behavior, nor should there be.
If you want to trigger when the user's registration in your app has completed, you should respond to events that signal that. So if registration means that the user is written to the database, you can use a onSnapshot listener on the database to detect user registration.
You could even combine the two:
Use an auth state change listener to detect when the sign in completes.
Inside that auth state listener, then attach a snapshot listener for the user's registration document.

How to check the particular field in the firestore after authentication in flutter?

I have a field in firestore "isEnabled" it contains boolean value. So if it is equal to true then the user login to the app he prompted to the home page.
If the field "isEnabled" is equal to false then a message should be displayed your account is disabled.
For now I am able to do login and after that the field is checked and if it is false the user is logged out.
Is there any way in which first authentication takes place, then the user is taken to a page where it shows "Checking few more details.." kind of a loading page and if the "isEnabled" field is false it will show your account has been disabled.
I tried doing the checking of the field before but it's not possible and after
AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
As soon as the above line is executed the user is obviously prompted to the home page.
What I am able to achieve that code is shown below -
Future signInWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
print(user.uid);
final firestore = Firestore.instance;
/*
retrieving the fields from the database for checking the isEnabled field
*/
await firestore.collection("admins").document(user.uid).get().then((resp) {
logoutToast();
if ((resp.data["isEnabled"]) == false) {
disableToast();
signOut();
} else {
loginToast();
}
return user;
});
//print(qn);
//return user;
} catch (error) {
print(error.toString());
return null;
}
}
Thanks !!
You can always disable a user in Firebase Auth and he won't be able to login at all.
If you still want to go your way and check a isEnabled on Firebase Firestore, than make the login and DON'T direct the user to the Home Screen, instead direct him to a Verification Screen and if isEnabled is true he goes to the Home Screen, if it's false you log him out.
Another solution is to have a Dialog shown in the Home Screen after the first time it's displayed and that Dialog will run the verification logic.

FirebaseAuth current user is not returning null after calling sign out?

I am trying to logout user and switch the widgets but after calling the following function -
void logoutUser() async {
await FirebaseAuth.instance.signOut();
}
If I check for the current user, it's returning user object but with null id -
FirebaseAuth.instance.currentUser()
I try to kick the user out to main.dart after logout which checks if user is signed in or not and loads a correct widget. Does anyone have any idea why currentUser() isn't returning null after calling signOut()?
_auth.currentUser() is probably returning an anonymous FirebaseUser object. Check the isAnonymous property.
Example :
auth.currentUser().then((user) {
if (user == null || user.isAnonymous) {
// what will you do?
return;
}
setState(() {
_uid = user.uid;
});
});
Yet, I would highly recommend to monitor the onAuthStateChanged stream instead. This way you will be informed when the user logs in or logs out immediately.
Check this article, it covers it in depth.

Firebase confirmation email not being sent

I've set up Firebase email/password authentication successfully, but for security reasons I want the user to confirm her/his email.
It says on Firebases website:
When a user signs up using an email address and password, a confirmation email is sent to verify their email address.
But when I sign up, I doesn't receive a confirmation email.
I've looked and can only find a code for sending the password reset email, but not a code for sending the email confirmation.
I've looked here:
https://firebase.google.com/docs/auth/ios/manage-users#send_a_password_reset_email
anyone got a clue about how I can do it?
I noticed that the new Firebase email authentication docs is not properly documented.
firebase.auth().onAuthStateChanged(function(user) {
user.sendEmailVerification();
});
Do note that:
You can only send email verification to users object whom you created using Email&Password method createUserWithEmailAndPassword
Only after you signed users into authenticated state, Firebase will return a promise of the auth object.
The old onAuth method has been changed to onAuthStateChanged.
To check if email is verified:
firebase.auth().onAuthStateChanged(function(user) {
if (user.emailVerified) {
console.log('Email is verified');
}
else {
console.log('Email is not verified');
}
});
After creating a user a User object is returned, where you can check if the user's email has been verified or not.
When a user has not been verified you can trigger the sendEmailVerification method on the user object itself.
firebase.auth()
.createUserWithEmailAndPassword(email, password)
.then(function(user){
if(user && user.emailVerified === false){
user.sendEmailVerification().then(function(){
console.log("email verification sent to user");
});
}
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorCode, errorMessage);
});
You can also check by listening to the AuthState, the problem with the following method is, that with each new session (by refreshing the page),
a new email is sent.
firebase.auth().onAuthStateChanged(function(user) {
user.sendEmailVerification();
});
The confirmation email could be in your spam folder.
Check your spam folder.
You can send verification email and check if was verified as follow into the AuthListener:
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
//---- HERE YOU CHECK IF EMAIL IS VERIFIED
if (user.isEmailVerified()) {
Toast.makeText(LoginActivity.this,"You are in =)",Toast.LENGTH_LONG).show();
}
else {
//---- HERE YOU SEND THE EMAIL
user.sendEmailVerification();
Toast.makeText(LoginActivity.this,"Check your email first...",Toast.LENGTH_LONG).show();
}
} else {
// User is signed out
Log.d(TAG, "onAuthStateChanged:signed_out");
}
// [START_EXCLUDE]
updateUI(user);
// [END_EXCLUDE]
}
};
if you're using compile "com.google.firebase:firebase-auth:9.2.0" and
compile 'com.google.firebase:firebase-core:9.2.0' the method sendEmailVerification() will not be resolved until you update to 9.8.0 or higher. It wasted most of time before I figured it out.
I have been looking at this too. It seems like firebase have changed the way you send the verification. for me
user.sendEmailVerification()
did not work.
If you get an error such as user.sendEmailVerification() doesn't exist.
use the following.
firebase.auth().currentUser.sendEmailVerification()
It's not the answer to the question but might help someone.
Don't forget to add your site domain to the Authorised domains list under Sign-in-method
You could send a verification email to any user whose email is linked to the Firebase Auth account. For example, in Flutter you could do. something like :
Future<void> signInWithCredentialAndLinkDetails(AuthCredential authCredential,
String email, String password) async {
// Here authCredential is from Phone Auth
_auth.signInWithCredential(authCredential).then((authResult) async {
if (authResult.user != null) {
var emailAuthCredential = EmailAuthProvider.getCredential(
email: email,
password: password,
);
authResult.user
.linkWithCredential(emailAuthCredential)
.then((authResult,onError:(){/* Error Logic */}) async {
if (authResult.user != null) {
await authResult.user.sendEmailVerification().then((_) {
debugPrint('verification email send');
}, onError: () {
debugPrint('email verification failed.');
});
}
});
}
});
}

Resources