1
votes

I'm having difficulty trying to use FB Login credentials to log into a Firebase Auth account when an account with the same email address already exists. Ultimately I want to link the two auth providers to the same Firebase user account but presently I can't get the FB credential to login to Firebase, if one already exists, using the code below which I think follows the Firebase documentation exactly. I have already restricted the ability for a single user (email) to have multiple accounts based on Auth Provider. When I delete the 'original' Firebase user account, Facebook login is able to login and create a user account as expected so the issues appears to only occur when there's already a Firebase auth account with the same email address from a different auth provider

Scenario

In the scenario I'm testing I've already created an email/password account (original) and am trying to add my FB account via FB Login (that uses the same email address as the email/password account) to that original account. I'm able to get the FB AccessToken and FB credential but when I pass it into Auth.auth().sign(with: credential..... it always errors out with the following error:

Error

Error Domain=FIRAuthErrorDomain Code=17012 "An account already exists with the same email address but different sign-in credentials. Sign in using a provider associated with this email address." UserInfo={FIRAuthErrorUserInfoNameKey=ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL, [email protected], FIRAuthErrorUserInfoUpdatedCredentialKey=, NSLocalizedDescription=An account already exists with the same email address but different sign-in credentials. Sign in using a provider associated with this email address.

Code

@IBAction func fbLoginButton(_ sender: FBButton) {

    let loginManager = LoginManager()
    loginManager.logIn(permissions: ["public_profile", "email"], from: self) { (result, error) in
        if error != nil {
            return
        }

        print(result)
        guard let accessToken = AccessToken.current else {
            print("Failed to get access token")
            return
        }
        print(accessToken)

        let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
        print(credential)

        Auth.auth().signIn(with: credential, completion: { (firebaseUser, error) in //Can't get passed this point when another account with the same email address already exists
            if error != nil {
                print("Could not login into Firebase using FB credentials")
                return
            }

            print("This is the FirebaseUser after FB Login: \(firebaseUser)")

            firebaseUser?.user.link(with: credential, completion: { (authResult, error) in
                if error != nil {
                    print("Firebase Auth Providers not linked")
                    return
                }

                //                let prevUser = Auth.auth().currentUser
                //                Auth.auth().signIn(with: credential) { (authResult, error) in
                //                  if let error = error {
                //                    // ...
                //                    return
                //                  }
                //                  // User is signed in
                //                  // ...
                //                }
                //                            // Merge prevUser and currentUser accounts and data
                //
                // ...

                //This user has a profile, go to tab controller
                let tabBarVC = self.storyboard?.instantiateViewController(withIdentifier: Constants.Storyboard.tabBarController)

                self.view.window?.rootViewController = tabBarVC
                self.view.window?.makeKeyAndVisible()

            })
        })
    }
}
1
User needs to login using the primary credential (One the account already exists for) and then grab the credential from the secondary to link. They can't login with the secondary because the primary account already exists.Kato
Can you explain the flow of that in a little more detail? My welcome screen allows you to login into FB or Sign up for an Account or Login using Email. If they login using the email (which is the original) the only way to get back to that FB login screen would be to logout which would remove the auth from the Login using Email. This seems like the typical UX of most logins I've seen. Are you suggesting a different flow?akash23a

1 Answers

0
votes

In case anyone else runs into the same issue.

It appears there's no way to link Firebase Auth providers 'on the fly' by having the new auth provider that has the same email address as an existing Firebase Auth account via the login flow. Therefore, my solution was to create a 'link facebook to your profile' button in the profile after they have logged in using their email/password. That button then creates the linkage in Firebase to recognize facebook as secondary auth provider for that account. For testing I logged out and used the 'Login with facebook' button and all works as expected.

I think this UX greatly diminishes the utility of the feature but after weeks of research its the best I could come up with.

Code on the Profile to link facebook to email/password original account

    @IBAction func fbLoginButton(_ sender: FBButton) {

    let loginManager = LoginManager()
    loginManager.logIn(permissions: ["public_profile", "email"], from: self) { (result, error) in
        if error != nil {
            return
        }

        print(result)

        guard let accessToken = AccessToken.current else {
            print("Failed to get access token")
            return
        }
        print(accessToken)

        let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
        print(credential)

        if let user = Auth.auth().currentUser {
            print("This is the user:  \(user)")
            // [START link_credential]
            user.link(with: credential) { _, error in
                // [START_EXCLUDE]
                if let error = error {
                    print("FB did not link to account.  \(error)")
                    return
                }
            }
        }
    }
}

Code on Login page (below) email/password login

Don't forget to add the LoginButtonDelegate to the View Controller

@IBAction func fbLoginButton(_ sender: FBButton) {

    let loginManager = LoginManager()
    loginManager.logIn(permissions: ["public_profile", "email"], from: self) { (result, error) in
        if error != nil {
            return
        }

        guard let accessToken = AccessToken.current else {
            print("Failed to get access token")
            return
        }

        let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)

        Auth.auth().signIn(with: credential) { (authResult, error) in

            if let error = error {
                // ...
                return
            }

            let user = authResult?.user

            //Check if user is nill
            if user != nil {

                //This means that we have a user, now check if they have a User document
                UserService.getUserProfile() { (u) in

                    if u == nil {

                        //No profile, go to Profile Controller
                        self.performSegue(withIdentifier: Constants.Segue.profileViewController, sender: self)
                    }
                    else {

                        //Save the logged in user to local storage
                        LocalStorageService.saveCurrentUser(user: u!)

                        //This user has a profile, go to tab controller
                        let tabBarVC = self.storyboard?.instantiateViewController(withIdentifier: Constants.Storyboard.tabBarController)

                        self.view.window?.rootViewController = tabBarVC
                        self.view.window?.makeKeyAndVisible()

                    }

                }

            }

        }

    }
}

For the button itself I added a button using storyboard to the View and used the customer class 'FBSDKButton' and styled it accordingly:

    let fbButtonText = NSAttributedString(string: "Link facebook to Profile")
    fbLoginButton.setAttributedTitle(fbButtonText, for: .normal)

and

    let fbButtonText = NSAttributedString(string: "Login with facebook")
    fbLoginButton.setAttributedTitle(fbButtonText, for: .normal)

Lastly, be sure you import the appropriate libraries; I used: import FirebaseAuth import FBSDKCoreKit import FacebookCore import FacebookLogin import FBSDKLoginKit import Firebase