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

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)
}

Related

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.
}

How do I handle Sign In Exceptions?

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.

Facebook login in iOS (Swift 4) - Get permissions and store in Firebase

I've found some good resources on Stackoverflow and youtube helping getting around the fact that the Facebook iOS SDK descriptions are not up to date. I've now successfully managed to create the Facebook login feature and a new user is registered in Firebase. However my current issue is two fold.
1) I understand that Firebase do NOT store the email of the user under Authentication if the user log in with Facebook. My question is - how to get the email from Facebook so that I can store it under the users profile, and how do I ensure that a user that has signed in / logged in with Facebook one day and by email another day are the same user?
2a) The .userFriends info is a list of friends that also use the app - I'm struggling to understand what info that Facebook provide and how I can use this to suggest other friends the user can follow in the app.
I've read the Facebook SDK info! But can someone help translating this into what it means in terms of Swift 4...
2B) Not knowing the data structure - I'm thinking of storing .userFriends into a new node - but unsure if I should do it under the specific user profile, or denormalise it and put it under root with the user uid as the identifier... let me know your thoughts please..
Working code - issue is with the commented section
#objc func loginButtonClicked() {
let loginManager = LoginManager()
loginManager.logIn(readPermissions: [.publicProfile, .email, .userFriends], viewController: self) { loginResult in // request access to user's facebook details
switch loginResult {
case .failed(let error):
print(error)
case .cancelled:
print("User cancelled login.")
case .success(let grantedPermissions, let declinedPermissions, let accessToken):
print(grantedPermissions)
print(declinedPermissions)
// Check permissions granted and declined add do action depending... eg. get name and surname for profile
// if FacebookAccessToken.grantedPermissions = {
// // TODO: publish content.
// }
// else {
// var loginManager = LoginManager()
// loginManager.logIn(readPermissions: [.publicProfile], viewController: self) { loginResult in
// //TODO: process error or result.
// }
// }
FacebookAccessToken = accessToken
let credential = FacebookAuthProvider.credential(withAccessToken: (FacebookAccessToken?.authenticationToken)!)
Auth.auth().signIn(with: credential) {( user, error) in
if let error = error {
print(error)
return
}
let currentUser = Auth.auth().currentUser
// Navigates back
self.dismiss(animated: true, completion: {});
self.navigationController?.popViewController(animated: true);
print("Successfully logged in user with Facebook")
}
}
}
}

Firebase. Change `username`(email) of registered user

My iOS application uses Firebase login by username and password. But I would like to give a possibility to change the username in settings.
The question is, does Firebase support changing a username?
Update
username means email
If you want to change the displayName you can use this code:
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = "DoesData"
changeRequest?.commitChanges { (error) in
// ...
}
If you want to change the email address you can use:
currentUser?.updateEmail(email) { error in
if let error = error {
print(error)
}
else {
// Email updated
}
}
I know you have to reauthenticate if you update a users password, but I'm not sure if you need to do that for email changes as well.
This code can help with authentication:
let credential = FIREmailPasswordAuthProvider.credentialWithEmail(email, password: password)
let currentUser = FIRAuth.auth()?.currentUser
currentUser?.reauthenticateWithCredential(credential) { error in
if let error = error {
// An error happened.
} else {
// User re-authenticated.
}
}
You may also want to look at documentation, this question, and this question
I guess you're talking about the email and password auth method.
You can change the email (which is the username) by updating it directly from code. Hope this helps!

Get Firebase User.ProviderID on auth/wrong-password when using signInWithEmailAndPassword

I have an Application that uses Firebase for Authentification.
I allow users to sign in with either Google/Facebook/Twitter or using an Email and Password.
Additionally I have activated within Firebase that Users can only create one account per email.
I want to cover the Following case:
A users signs up with facebook and gets a user account created with their facebook email (e.G. facebookuser#gmail.com).
A few days later the user comes back to the app, but forgot that he signed up using facebook and tries loging in with their email adress facebookuser#gmail.com and their usual password instead.
The firebase.auth().signInWithEmailAndPassword(email, password) method throws an auth/wrong-password error, as no password was given using the facebook login method but the email does exist.
Instead of showing a useless "Wrong password or the account corresponding to the email does not have a password set." error I would like to check instead which provider was used for signing up and reminding the user to sign in with the provider instead.
Unfortunately there doesn't seem to exist a method to get the User.ProviderID for a given email or to understand if the auth/wrong-password error was given because the user typed in a wrong password or if there was no password given in first place as the user signed up with an OAuthProvider instead.
Okay, I have missed the fetchProvidersForEmail method.
Just for Reference: (not the final code I will be using)
Uses signInWithEmailAndPassword to check if a User exists
If it doesn't it creates it using createUserWithEmailAndPassword
If a user exits, it tries to login and catches errors
If the error is auth/wrong-password it checks with fetchProvidersForEmail if the user has used a Provider to sign up
firebase.auth().signInWithEmailAndPassword(email, password).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
// User not found? Create user.
if ( errorCode === 'auth/user-not-found' ) {
firebase.auth().createUserWithEmailAndPassword(email, password).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
if ( errorCode == 'email-already-in-use' ) {
alert('You already have an account with that email.');
} else if ( errorCode == 'auth/invalid-email' ) {
alert('Please provide a valid email');
} else if ( errorCode == 'auth/weak-password' ) {
alert('The password is too weak.');
} else {
alert(errorMessage);
}
console.log(error);
});
// Wrong Password Error
} else if ( errorCode === 'auth/wrong-password' ) {
// Check if User has signed up with a OAuthProvider
firebase.auth().fetchProvidersForEmail(email).then(function( result ){
// … show OAuthProvider Login Button
});
alert('Wrong password. Please try again');
} else {
alert( errorMessage );
}
console.log( error );
});

Resources