How do I handle Sign In Exceptions? - firebase

Hello I am new to flutter, I am making a social media app in which users are to signIn using email and password. I want to know how do I show users login errors such as Incorrect Password / Incorrect email etc. I am using firebase as back end database for application
I have successfully created both the login and registration parts of the application, but I am unable to show different errors. here is is my code someone please help me
Future LogIn(BuildContext context) async {
FirebaseUser user = (await _Auth.signInWithEmailAndPassword(
email: getEmail(), password: getPass()))
.user;
try {
if (user.isEmailVerified) {
Navigator.pushNamed(context, HomeScreen.id);
//Navigator.pushNamed(context, HomeScreen.id);
ClearAllInfo();
} else {
user.sendEmailVerification();
Alert("EMAIL NOT VERIFIED").show(); //email not verified alert
}
} catch (e) {}
}

The documentation for signInWithEmailAndPassword lists all possible errors:
ERROR_INVALID_EMAIL
ERROR_WRONG_PASSWORD
ERROR_USER_NOT_FOUND
ERROR_USER_DISABLED
ERROR_TOO_MANY_REQUESTS
ERROR_OPERATION_NOT_ALLOWED
Now, you can test for these in your try-catch:
try {
FirebaseAuth.instance.signInWithEmailAndPassword(...); // Your signin call.
} on PlatformException catch (e) {
switch (e.code) {
case 'ERROR_WRONG_PASSWORD':
// Handle wrong password.
break;
// Add other cases.
default:
break;
}
}
Note that the catch block I added only catches PlatformExceptions. If you expect to see other exceptions, you might want to add another general catch block or on specific exceptions.

Related

Converting Firebase anonymous user to user with email and password - Flutter

I'm having trouble to convert an anonymous Firebase user to a registered user with email and password.
My code look's like this:
Future linkAnonymousAccount({
required String email,
required String password,
}) async {
try {
await _auth.currentUser!.linkWithCredential(
auth.EmailAuthProvider.credential(email: email, password: password),
);
// await refreshUser();
} on auth.FirebaseAuthException catch (e) {
throw LinkAnonymousAccountException(e.code);
}
}
It seem's like EmailAuthProvider.credential creates a new user (with new uuid and everything) instead of converting the current anonymous user.
So after creating an anonymous user with:
Future signInAnonymously() async {
try {
await _auth.signInAnonymously();
} on auth.FirebaseAuthException catch (e) {
throw SignInException(e.code);
}
}
And then calling the linkAnonymousAccount - method from above I get the following result in my firebase console:
But so I lose all user data like displayName and photoUrl and also all the data in my database (due to the different uuid's).
I'm not able to find a solution for this or an explanation why this is happening.
I found a video where this is not the case and the anonymous user is actually converted. But I use the same code as the guy in the video and get a different result.
Am I missing something? Has someone an explanation for this?

Apple sign in causes FIRAuthErrorUserInfoNameKey=ERROR_EMAIL_ALREADY_IN_USE (Code = 17007)

Using SwiftUI, Xcode12.5.1, Swift5.4.2, iOS14.7.1,
My Firebase-Email/Password Login-page shall be extended with other Login possibilities such as Apple-Login (eventually Google-login, Facebook-login etc).
My steps:
log in with Email/Password to Firebase
log out
log in with "Sign in with Apple"
--> Then I get the following error:
Error Domain=FIRAuthErrorDomain Code=17007
"The email address is already in use by another account."
UserInfo={NSLocalizedDescription=The email address is already in use by another account.,
FIRAuthErrorUserInfoNameKey=ERROR_EMAIL_ALREADY_IN_USE}
What I intended to do is to link the existing Email/Password-Firebase-Account to the Sign in with Apple-Account (as described here and here).
But for doing that I would need the error FIRAuthErrorUserInfoUpdatedCredentialKey that allows to retrieve the old user eventually.
In my case, I get ERROR_EMAIL_ALREADY_IN_USE which does not lead to any old user to be linked.
What do I have to do ?
Here is my code:
let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
Auth.auth().signIn(with: credential) { (authResult, error) in
if (error != nil) {
print(error?.localizedDescription as Any)
return
}
print("signed in with Apple...")
do {
// if user did log in with Email/Password previously
if let email = try THKeychain.getEmail(),
let password = try THKeychain.getPassword() {
let credential = EmailAuthProvider.credential(withEmail: email, password: password)
if let user = authResult?.user {
// here I am trying to link the existing Firebase-Email/Password account to the just signed-in with Apple account
user.link(with: credential) { (result, linkError) in
print(linkError) // this is where I get FIRAuthErrorUserInfoNameKey=ERROR_EMAIL_ALREADY_IN_USE
// unfortunately, the two accounts are not linked as expected due to this error !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// What is missing ??????????????????
loginStatus = true
}
}
} else {
loginStatus = true
}
} catch {
print(error.localizedDescription)
}
}
On the Firebase-documentation it sais:
Sign in with Apple will not allow you to reuse an auth credential to link to an existing account. If you want to link a Sign in with Apple credential to another account, you must first attempt to link the accounts using the old Sign in with Apple credential and then examine the error returned to find a new credential. The new credential will be located in the error's userInfo dictionary and can be accessed via the FIRAuthErrorUserInfoUpdatedCredentialKey key.
What does the part "...If you want to link a Sign in with Apple credential to another account, you must first attempt to link the accounts using the old Sign in with Apple credential..." exactly mean ? WHAT IS THE old Sign in with Apple credential ????????
And how would I do that ?
In fact, at the linking-call, I actually expected some sort of linkError.userInfo with an updated user to sign in with. But the linkError in my example only gives me the ERROR_EMAIL_ALREADY_IN_USE error without further userInfo.
As Peter Friese mentions in his Blog, I should somehow be able to retrieve a AuthErrorUserInfoUpdatedCredentialKey from the error.userInfo. But in my case, the linkError does not have any kind of such information - unfortunately!
Here is an excerpt of Peter's example: (again not applicable in my case for some unknown reason?????)
currentUser.link(with: credential) { (result, error) in // (1)
if let error = error, (error as NSError).code == AuthErrorCode.credentialAlreadyInUse.rawValue { // (2)
print("The user you're signing in with has already been linked, signing in to the new user and migrating the anonymous users [\(currentUser.uid)] tasks.")
if let updatedCredential = (error as NSError).userInfo[AuthErrorUserInfoUpdatedCredentialKey] as? OAuthCredential {
print("Signing in using the updated credentials")
Auth.auth().signIn(with: updatedCredential) { (result, error) in
if let user = result?.user {
// TODO: handle data migration
self.doSignIn(appleIDCredential: appleIDCredential, user: user) // (3)
}
}
}
}
}
Reversing the order of linking made me advance a tiny bit.
If I press the Sign in with Apple button, my code now logs in with Firebase-Email/Password first (i.e. the necessary credentials are taken from the Keychain). And on a second step, links with the Apple-credentials. And by doing so, the linking finally gives me the desired AuthErrorUserInfoUpdatedCredentialKey in the link-callback.
There I retrieve the updatedCredential to log in with Apple.
See code below.
HOWEVER, I STILL DON'T KNOW WHY AFTER LOGIN THIS WAY, MY DATA IS STILL MISSING ???????
HOW DOES THIS DATA-MIGRATION STEP WORK ???
Shouldn't the user.link(with: appleCredentials) { ... } do the job ?
What do I need to do in order to get the very same Firebase-Data, no matter the login method ???
let appleCredentials = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
do {
// if user did log in with Email/Password anytime before
if let email = try THKeychain.getEmail(),
let password = try THKeychain.getPassword() {
let firebaseEmailCredentials = EmailAuthProvider.credential(withEmail: email, password: password)
Auth.auth().signIn(with: firebaseEmailCredentials) { (authResult, error) in
if let user = authResult?.user {
user.link(with: appleCredentials) { (result, linkError) in
if let linkError = linkError, (linkError as NSError).code == AuthErrorCode.credentialAlreadyInUse.rawValue {
print("The user you're signing in with has been linked.")
print("Signing in to Apple and migrating the email/pw-firebase-users [\(user.uid)]` data.")
if let updatedCredential = (linkError as NSError).userInfo[AuthErrorUserInfoUpdatedCredentialKey] as? OAuthCredential {
print("Signing in using the updated credentials")
Auth.auth().signIn(with: updatedCredential) { (result, error) in
if let _ = result?.user {
print("signed in with Apple...")
// TODO: handle data migration
print("Data-migration takes place now...")
loginStatus = true
}
}
}
}
else if let error = error {
print("Error trying to link user: \(error.localizedDescription)")
}
else {
if let _ = result?.user {
loginStatus = true
}
}
}
}
}
} else {
// case where user never logged in with firebase-Email/Password before
Auth.auth().signIn(with: appleCredentials) { (result, error) in
if let _ = result?.user {
print("signed in with Apple...")
loginStatus = true
}
}
}
} catch {
print(error.localizedDescription)
}

Firebase Login and Login with Apple not linking to same user account

Using SwiftUI, Xcode12.5.1, Swift5.4.2, iOS14.7.1,
My Firebase-Login page shall be extended with other Login possibilities such as Apple-Login (eventually Google-login, Facebook-login etc).
I have an implementation of Firebase-Login that works well.
I extended the LoginView with the Sign in with Apple Button.
And this new Apple Login in its basic implementation also works.
Now the problem:
If I log in with Apple, I need to access the corresponding Firebase-user in order to query the correct user-data. Right now, login in with Apple works but the retrieved data is not the user-data of the corresponding Firebase-user.
What I want to achieve:
From a logout-state, I want to
a) Being able to log in with Firebase Email/Password and sometimes later want to log-out and log in again with Apple.
--> and for both cases, I would like to get the same user-data
b) Being able to log in with Apple and sometimes later want to log-out and log in again with Firebase Email/Password
--> and for both cases, I would like to get the same user-data
--- THE IDEA ----------
I learned from the Firebase documentation that there is a way to link two login-accounts that we are able to know that these two accounts are corresponding.
--- THE IMPLEMENTATION -----------
Below is my current implementation for the Apple login:
I learned that you can get userInformation of the corresponding other account in the error of the link-callback. But in my case, I get the wrong linkError:
My linkError:
The email address is already in use by another account.
Instead of:
AuthErrorCode.credentialAlreadyInUse
For me this doesn't make sense. Especially since I know that I already did log in before with Firebase-Email/Password. Then I logged out and now I tried to log in with Apple.
Shouldn't the link method recognise that I am allowed to have been logged in via Firebase-Email/Password before and shouldn't it be ok to have that email being used before ?? I don't understand this linkError.
Questions:
In the link-callback, why do I get the linkError The email address is already in use by another account. instead of AuthErrorCode.credentialAlreadyInUse ??
What do I need to change in order to make a) work ??
How does the implementation look for the b) workflow (i.e. if user logs in to Apple, then logs-out and logs in again with Firebase-Email/Password ??). How do I link the two accounts then ??
Here my code:
switch state {
case .signIn:
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print("Error authenticating: \(error.localizedDescription)")
return
}
do {
if let email = try THKeychain.getEmail(),
let password = try THKeychain.getPassword() {
let credential = EmailAuthProvider.credential(withEmail: email, password: password)
if let user = authResult?.user {
user.link(with: credential) { (result, linkError) in
if let linkError = linkError, (linkError as NSError).code == AuthErrorCode.credentialAlreadyInUse.rawValue {
print("The user you're signing in with has already been linked, signing in to the new user and migrating the anonymous users [\(user.uid)] tasks.")
if let updatedCredential = (linkError as NSError).userInfo[AuthErrorUserInfoUpdatedCredentialKey] as? OAuthCredential {
print("Signing in using the updated credentials")
Auth.auth().signIn(with: updatedCredential) { (result, error) in
if let user = result?.user {
// eventually do a data-migration
user.getIDToken { (token, error) in
if let _ = token {
// do data migration here with the token....
self.doSignIn(appleIDCredential: appleIDCredential, user: user)
}
}
}
}
}
}
else if let linkError = linkError {
// I END UP HERE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// WHY WHY WHY WHY WHY WHY WHY WHY ????????????????????????
print("Error trying to link user: \(linkError.localizedDescription)")
}
else {
if let user = result?.user {
self.doSignIn(appleIDCredential: appleIDCredential, user: user)
}
}
}
}
}
} catch {
print(error.localizedDescription)
}
if let user = authResult?.user {
if let onSignedInHandler = self.onSignedInHandler {
onSignedInHandler(user)
}
}
}
case .link:
// t.b.d.
case .reauth:
// t.b.d.
}

Check if an email already exists in Firebase Auth in Flutter App

I'm currently developing a flutter app that requires users to register before using it. I use Firebase Authentication and would like to check whether an email is already registered in the app.
I know the easy way to do it is to catch the exception when using the createUserWithEmailAndPassword() method (as answered in this question). The problem is that I ask for the email address in a different route from where the user is registered, so waiting until this method is called is not a good option for me.
I think the best option would be to use the method fetchProvidersForEmail(), but I can't seem to make it work.
How do I use that method? Or is there a better option to know if an email is already registered?
The error raised is a PlatformException
so you can do something as follows-
try {
_firbaseAuth.createUserWithEmailAndPassword(
email: 'foo#bar.com',
password: 'password'
);
} catch(signUpError) {
if(signUpError is PlatformException) {
if(signUpError.code == 'ERROR_EMAIL_ALREADY_IN_USE') {
/// `foo#bar.com` has alread been registered.
}
}
}
The following error codes are reported by Firebase Auth -
ERROR_WEAK_PASSWORD - If the password is not strong enough.
ERROR_INVALID_EMAIL - If the email address is malformed.
ERROR_EMAIL_ALREADY_IN_USE - If the email is already in use by a different account.
There is no such fetchProvidersForEmail method anymore in the current version of the firebase auth package. The equivalent one is now fetchSignInMethodsForEmail method which I think would be the best option to handle this case without executing any unnecessary operation.
fetchSignInMethodsForEmail
In docs, it's stated that this method returns an empty list when no user found, meaning that no account holds the specified email address:
Returns a list of sign-in methods that can be used to sign in a given
user (identified by its main email address).
This method is useful when you support multiple authentication
mechanisms if you want to implement an email-first authentication
flow.
An empty List is returned if the user could not be found.
Based on this, we could create our own method like the following one:
// Returns true if email address is in use.
Future<bool> checkIfEmailInUse(String emailAddress) async {
try {
// Fetch sign-in methods for the email address
final list = await FirebaseAuth.instance.fetchSignInMethodsForEmail(emailAddress);
// In case list is not empty
if (list.isNotEmpty) {
// Return true because there is an existing
// user using the email address
return true;
} else {
// Return false because email adress is not in use
return false;
}
} catch (error) {
// Handle error
// ...
return true;
}
}
I think the only possibility from within the app is attempting a login (signInWithEmailAndPassword) with that e-mail and check the result.
If it's invalid password, the account exists.
If it's invalid account, the account do not exist.
Error 17011
There is no user record corresponding to this identifier. The user may have been deleted
Error 17009
The password is invalid or the user does not have a password
As this is a kind of an ugly solution, you can justify this additional call using it to check it the e-mail formatting is correct (according to the firebase rules). If it doesn't comply it will throw a address is badly formatted and you can alert the user soon enough.
You can do these checks using the error codes with current versions of the plug-in.
There are many ways you can do that. As Sakchham mentioned, you could use that method. There is another method you could use which in my opinion is better and safer.
Since the password value will return ERROR_WEAK_PASSWORD, it is a create account method which you are calling which means that it's possible an account will be created if the account doesn't exist, in that case, I recommend personally using the sign in with email method.
I used this code below:
Future<dynamic> signIn(String email) async {
try {
auth = FirebaseAuth.instance;
await auth.signInWithEmailAndPassword(email: email, password: 'password');
await auth.currentUser.reload();
return true;
} on FirebaseAuthException catch (e) {
switch (e.code) {
case "invalid-email":
return 'Your username or password is incorrect. Please try again.';
break;
}
}
}
Leave down a comment if you have any suggestions.
I didn't think fetchProvidersForEmail() method is available in the firebase package. So we can show the appropriate message to the user. you can create more case if you need.
try {
await _auth.signInWithEmailAndPassword(
email: "Hello#worl.com",
password: "123456789"
);
} catch (e) {
print(e.code.toString());
switch (e.code) {
case "email-already-in-use":
showSnackBar(context,"This Email ID already Associated with Another Account.");
break;
}
}

Firebase ResetPassword issue

I am building user authentication in my website using Angular and Firebase's Email & Password authentication framework. This is NOT using the Firebase Simple Login Framework but the newly introduced native framework.
My code links to Firebase using:
<script src="https://cdn.firebase.com/js/client/1.1.0/firebase.js"></script>
I can create users, login and so on but the ResetPassword call fails with the following error.
Error: Firebase.resetPassword failed: First argument must be a valid object.
at Error (native)
at E (https://cdn.firebase.com/js/client/1.1.0/firebase.js:15:73)
at F.G.td (https://cdn.firebase.com/js/client/1.1.0/firebase.js:192:79)
at Object.resetPassword (http://localhost:8000/src/js/services/loginservice.js:78:27)
at k.$scope.resetPassword (http://localhost:8000/src/js/controllers.js:35:20)
at http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js:177:68
at http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js:171:237
at f (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js:194:174)
at k.$eval (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js:112:68)
at k.$apply (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js:112:346)
The code I use is from the firebase example as follows:
ref.resetPassword(email, function(error) {
if (error === null) {
console.log("Password reset email sent successfully");
} else {
console.log("Error sending password reset email:", error);
}
});
I have verified that a valid email id is being passed in.
Can you please let me know what the issue is?
Thanks in advance
vm
According to the firebase documentation, you should be passing in an object not a string.
https://www.firebase.com/docs/web/api/firebase/resetPassword.html
var credentials = {email: email};
ref.resetPassword(credentials, function(error) {
if (error === null) {
console.log("Password reset email sent successfully");
} else {
console.log("Error sending password reset email:", error);
}
}
I have found that using an object no longer works, it would keep giving me "First argument must contain the key "email", even then the object was as the answer above.
After much frustration i got it working passing the email parameter as per the firebase docs.
$scope.resetPassword = function(email){
console.log("made in to auth method for reset passowrd with email - " + email);
ref.resetPassword({
email: email
}, function(error) {
if (error) {
switch (error.code) {
case "INVALID_USER":
console.log("The specified user account does not exist.");
break;
default:
console.log("Error resetting password:", error);
}
} else {
console.log("Password reset email sent successfully!");
}
});
}

Resources