FirebaseAuth.signInWithEmailAndPassword return null user - firebase

I am developing on the flutter platform and using FirebaseAuth library for dart.
When I call FirebaseAuth.signInWithEmailAndPassword(), the user returned in the AuthResult is null.
The problem is that no error was thrown. So I am really confused. According to the docs, there is a list of error code that can be thrown. But in this instance it is just silently failing. Does anyone know why it could be silently failing without communicating any sort of error back?
My code looks as follows...
try {
final AuthResult result = await FirebaseAuth.instance.signInWithEmailAndPassword(
email:email,
password:password
);
final FirebaseUser user = result.user;
if (user != null) {
callback(RequestUserSignInResult.Fail);
return;
}
_user = user;
callback(RequestUserSignInResult.Success);
}
catch (e) {
print("CODE:"+e.code+'\n');
print("MESSAGE:"+e.message+'\n');
callback(RequestUserSignInResult.Success);
}

You seem to have reversed your conditions when checking for the presence of a user in the AuthResult response. But you could also simplify your code like this:
try {
FirebaseAuth.instance.signInWithEmailAndPassword(
email:email,
password:password
).then((authResult){
if(authResult.user != null){
_user = authResult.user;
callback(RequestUserSignInResult.Success);
}else{
callback(RequestUserSignInResult.Fail);
}
});
} catch (e) {
print("CODE:"+e.code+'\n');
print("MESSAGE:"+e.message+'\n');
callback(RequestUserSignInResult.Fail);
}

Related

Flutter Firebase login error but still being logged in?

EDIT: The login functions is somehow called twice, once with the correct credentials and the other time the email and password String are empty.
This weird hack seems to fix it, but I cannot see why the login function is called twice:
if (email.isEmpty && password.isEmpty) {
return;
}
I have a weird problem that is caused by Firebase-Auth, I believe. Quick summary of the process:
User logs in normally, then authenticates with the local_auth package using biometrics
If that is successful, the login credentials (email, password) are stored on the device using FlutterSecureStorage
Then, on every new app startup, the user will be prompted with the local_auth and if that is successful, I call the login method with the credentials read from the device.
Here comes the error: I get a FirebaseAutException with the error message: given String is empty or null, but then I am being logged in, even though there was an error.
This is the login code:
void login({required String email, required String password}) async {
try {
final _result = await _auth.signInWithEmailAndPassword(
email: email.trim(),
password: password.trim(),
);
if (_result.user != null) {
print("HERE1");
if (!_result.user!.emailVerified) {
Get.to(() => EmailVerificationScreen());
}
if (!await credentialsSaved) {
Get.to(
() => BiometricsPage(
email: email.trim(),
password: password.trim(),
),
);
}
Get.find<UserController>().setUser = await Database().getUser(
_result.user!.uid,
);
loggedIn.value = true;
}
} on FirebaseAuthException catch (e) {
print("HERE2");
Get.snackbar(
"Error logging in",
e.message!,
snackPosition: SnackPosition.BOTTOM,
snackStyle: SnackStyle.FLOATING,
margin: EdgeInsets.all(10),
);
}
}
The print statements occur in the following order:
HERE1
HERE2
EDIT: After a few tries, I saw that sometimes the print-order was exactly the other way around :/
This is the code I use for retrieving the credentials from the device:
void checkLocalBiometrics() async {
if (await credentialsSaved) {
var localAuth = LocalAuthentication();
bool canCheckBiometrics = await localAuth.canCheckBiometrics;
if (canCheckBiometrics) {
var didAuthenticate = await localAuth.authenticate(
localizedReason: "-----------------",
biometricOnly: true,
stickyAuth: true,
);
if (didAuthenticate) {
var secureStorage = FlutterSecureStorage();
var storedEmail = await secureStorage.read(key: "email");
var storedPassword = await secureStorage.read(key: "password");
login(email: storedEmail!, password: storedPassword!);
}
}
}
}
This is the credentialsSaved method:
Future<bool> get credentialsSaved async {
var secureStorage = FlutterSecureStorage();
var storedEmail = await secureStorage.read(key: "email");
return storedEmail != null;
}
I can guarantee that the result of the secureStorage.read(...) is not null since I check that in the credentialsSaved method. What am I missing here?
I finally found the solution. Turns out that the LoginButton I was using in my LoginPage has an open issue about functions being called twice. (rounded_login_button bug). Can't believe that that was it all the time..

Firebase + Flutter: can't lock access to unverified email accounts

I'd like to block out people who didn't verify their email so i figured out this code for sign up:
// sign up
Future signUp(String email, String password) async {
try {
await _auth.createUserWithEmailAndPassword(
email: email, password: password);
} catch (e) {
print('An error has occured by creating a new user');
print(
e.toString(),
);
}
try {
final FirebaseUser _user = await _auth.currentUser();
await _user.sendEmailVerification();
} catch (error) {
print("An error occured while trying to send email verification");
print(error.toString());
}
try {
await _auth.signOut();
} catch (err) {
print(err);
}
}
and this for sign in:
//Sign In with Email and Pass
Future signInWithEmailAndPassword(String email, String password) async {
FirebaseUser _user = await FirebaseAuth.instance.currentUser();
if (_user != null && _user.isEmailVerified == true) {
try {
await _auth.signInWithEmailAndPassword(
email: email, password: password);
return _user;
} catch (e) {
return null;
}
} else {
return null;
}
}
_auth is just an instance of FirebaseAuth.
The problem is that i can login even if i didnt verify the email.
Firebase Auth doesn't stop accounts from signing in if the user hasn't verified their email address yet. You can check that property _user.isEmailVerified to find out the state of that validation after the user signs in, and you can determine from there what the user should see.
isEmailVerified can be a little bit of trouble to get working correctly.
Make sure you are calling
await FirebaseAuth.instance.currentUser()..reload();
before your are calling isEmailVerified also in my own experience and I don't know if this is just something I was doing wrong but this did not work from my Auth class this did not start working until I put the code directly in initState() of my widget that checks whether the user is verified. Like I said that part might have been something I did wrong. Like stated this will not listen for change you must check yourself either periodically or at a point that you know email is verified.
Future(() async {
_timer = Timer.periodic(Duration(seconds: 10), (timer) async {
await FirebaseAuth.instance.currentUser()
..reload();
var user = await FirebaseAuth.instance.currentUser();
if (user.isEmailVerified) {
timer.cancel();
Navigator.of(context).popAndPushNamed(HearingsScreen.routeName);
}
});
});
So it checks every 10 seconds to see if the user has verified their email not the most elegant solution. The page I have this on just displays a message 'Please verify your email' so its not like this is interrupting other code. If your app is performing other tasks this might not be an option for you. If you want to play around with isEmailVerified go ahead but i spent a week of headaches until i settled on this.

Flutter: PlatformException thrown by FireBase won't get caught

I have a function that is used to sign in to Firebase using firebase_auth, however, whenever an exception is thrown it isn't getting caught and still appears in the Android Studio console nor do the print statements in the catch block ever run.
How do I fix this?
signIn({String email, String password}) {
print('listened');
try {
FirebaseAuth.instance.signInWithEmailAndPassword(
email: email, password: password);
}
on PlatformException catch (signUpError) {
print(signUpError.code);
if (signUpError.code == 'ERROR_WEAK_PASSWORD') {
print('Weak Password');
}else if(signUpError.code=='ERROR_USER_NOT_FOUND'){
print('Invalid Username');
}
else{
print(signUpError.toString());
}
}
}
signInWithEmailAndPassword returns a Future<AuthResult> (it is asynchronous), therefore you need to use the catchError method to catch the error when calling an asynchronous method:
FirebaseAuth.instance.signInWithEmailAndPassword(email: email, password: password).then((result) {
print(result);
})
.catchError((error) {
print("Something went wrong: ${error.message}");
});
Check the following:
https://api.dart.dev/stable/2.3.0/dart-async/Future/catchError.html
https://medium.com/firebase-tips-tricks/how-to-use-firebase-authentication-in-flutter-50e8b81cb29f
The try catch blocks will work if the Firebase call is within an async function and the await statement is used when calling Firebase.
For example, in the following code an error getting the token will be trapped by the on PlatformException catch (... block but an error writing the token to FB RTDB won't be:
Future<void> _saveDeviceToken() async {
try {
final _currentUser = _firebaseAuth.currentUser;
final fcmToken = await _firebaseMessaging.getToken();
if (fcmToken != null) {
// Save the token to Firebase - NO AWAIT STATEMENT
globals.firebaseDatabase
.reference()
.child("pushTokens")
.child("${_currentUser.uid}")
.set({"token": fcmToken});
}
} on PlatformException catch (error, stackTrace) {
print("error: $error");
}
}
whereas adding the await statement, as in the following code, will trap errors in writing to FB RTDB as well:
Future<void> _saveDeviceToken() async {
try {
final _currentUser = _firebaseAuth.currentUser;
final fcmToken = await _firebaseMessaging.getToken();
if (fcmToken != null) {
// Save the token to Firebase - AWAIT STATEMENT ADDED
await globals.firebaseDatabase
.reference()
.child("pushTokens")
.child("${_currentUser.uid}")
.set({"token": fcmToken});
}
} on PlatformException catch (error, stackTrace) {
print("error: $error");
}
}
If you don't want to or can't use await then, as per Peter's answer, the solution is to use the catchError statement instead of try catch.

Firebase/Flutter: reload() not refreshing user.isEmailVerified

I'm sending a verification link when a user registers in the app, but when I try to create a stream that listens for when the user has clicked the verify link in the email.
I'm aware that I somehow need to refresh the user token, but I can't seem to get it to work. I thought reload() method was the one, but maybe I'm just not implementing it correctly.
The problem is that the Stream always returns isEmailVerified == false, only way to make it true is for the user to log out and log in again, which is something I'd like to avoid. How do I do this?
I've created this future:
//CHECKS IF EMAIL IS VERIFIED
Future<bool> checkIfEmailIsVerified() async {
FirebaseUser currUser = await _auth.currentUser();
await currUser.reload();
currUser = await _auth.currentUser();
final bool flag = currUser.isEmailVerified;
if (currUser != null) {
return flag;
} else {
return false;
}
}
and this stream:
//IS EMAILVERIFIED STREAM
Stream<EmailVerified> get emailVerified async* {
final bool isEmailVerified = await checkIfEmailIsVerified();
yield EmailVerified(isEmailVerified);
}
Unfortunately it's necessary to get fresh instance of the user after reload:
User user = FirebaseAuth.instance.currentUser;
if (user != null) {
await user.reload();
user = FirebaseAuth.instance.currentUser;
if (user.emailVerified) {
...
}
}
Try using a single await over chained futures
FirebaseUser currUser = await _auth.currentUser().then((u) => u.reload().then((_) => _auth.currentUser()));
final bool flag = currUser.isEmailVerified;

Getting the result of a method Future<T> flutter

I have a method
Future<FirebaseUser> getCurrentUser() async {
FirebaseUser user = await _firebaseAuth.currentUser();
return user;
}
I want to be able to call it in some other module by doing :
if (class.getCurrentUser() != null) {
// Do something
}
I cannot seem to figure out how to get the actual value, and not the Future object storing the value. In C++ for instance, I can just do future.get(), which will block and return the value to me. Is there an equivalent in flutter? I am new to the language and have searched for hours and cannot seem to find a solution to this exact problem.
From your code, it seems you actually need 1 await only:
Future<FirebaseUser> getCurrentUser() async {
return _firebaseAuth.currentUser();
}
if (await class.getCurrentUser() != null) {
// Do something
}
You have to await for the result in your method call as well like this:
FirebaseUser currentUser = await class.getCurrentUser();
if (currentUser != null) {
// Do something
}

Resources