使用 Firebase Auth 链接多个 Auth Provider

Linking multiple Auth Providers using Firebase Auth

我在尝试使用 FB 登录凭据登录 Firebase Auth 帐户时遇到困难,因为已经存在具有相同电子邮件地址的帐户。最终我想 link 两个身份验证提供程序到同一个 Firebase 用户帐户,但目前我无法获得 FB 凭据来登录 Firebase,如果已经存在的话,使用下面的代码,我认为它遵循 Firebase 文档确切地。我已经限制了单个用户(电子邮件)拥有基于 Auth Provider 的多个帐户的能力。当我删除 'original' Firebase 用户帐户时,Facebook 登录能够按预期登录并创建用户帐户,因此问题似乎仅在已经存在具有来自不同身份验证提供商的相同电子邮件地址的 Firebase 身份验证帐户时才会出现

场景

在我正在测试的场景中,我已经创建了一个 email/password 帐户(原始),并且正在尝试通过 FB 登录添加我的 FB 帐户(使用与 email/password 帐户)到那个原始帐户。我能够获得 FB AccessToken 和 FB 凭证,但是当我将它传递给 Auth.auth().sign(with: credential..... 它总是出错并出现以下错误:

错误

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, FIRAuthErrorUserInfoEmailKey=akash11x@gmail.com, FIRAuthErrorUserInfoUpdatedCredentialKey=, NSLocalizedDescription=已存在相同的帐户电子邮件地址但不同的登录凭据。使用与此电子邮件地址关联的提供商登录。

代码

@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()

            })
        })
    }
}

以防其他人遇到同样的问题。

似乎无法 link Firebase Auth 提供程序 'on the fly' 通过登录流程让新的 auth 提供程序与现有 Firebase Auth 帐户具有相同的电子邮件地址。因此,我的解决方案是在他们使用 email/password 登录后在配置文件中创建一个 'link facebook to your profile' 按钮。该按钮然后在 Firebase 中创建 linkage 以将 facebook 识别为该帐户的辅助身份验证提供程序。为了进行测试,我注销并使用了 'Login with facebook' 按钮,一切都按预期工作。

我认为这个 UX 大大降低了该功能的实用性,但经过几周的研究,它是我能想到的最好的。

个人资料上的代码到 link facebook 到 email/password 原始帐户

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

登录页面代码(下)email/password登录

不要忘记将 LoginButtonDelegate 添加到视图控制器

@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()

                    }

                }

            }

        }

    }
}

对于按钮本身,我使用故事板向视图添加了一个按钮,并使用了客户 class 'FBSDKButton' 并相应地设置了样式:

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

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

最后,请务必导入适当的库;我用了: 导入 FirebaseAuth 导入 FBSDKCoreKit 导入 FacebookCore 导入 Facebook 登录 导入 FBSDKLoginKit 导入 Firebase