Повторное использование учетных данных для входа в Apple в Firebase iOS - PullRequest
2 голосов
/ 13 марта 2020

После прочтения в Документации Firebase , как управлять ошибками учетной записи с тем же адресом электронной почты, но разными учетными данными, я изменил свой код, как рекомендует документация Firebase, но я все еще нахожу проблемы

Когда при аутентификации в Facebook возникает ошибка AuthErrorCode.accountExistsWithDifferentCredential.rawValue, она получает временные учетные данные (это может произойти в (error! As NSError) .userInfo [AuthErrorUserInfoUpdatedCredentialKey])

, а затем ищет в Firebase провайдеров, которых пользователь использовал до теперь с Auth.auth().fetchSignInMethods

Когда он встречает (в моем примере) провайдера "apple.com" , документация говорит аутентифицировать пользователя и делать ( если все идет хорошо) получена связь с провайдером с использованием временного удостоверения .

Теперь моя проблема в том, что когда Auth.auth (). fetchSignInMethods встречается с провайдером "apple.com" Я не могу пройти аутентификацию с этим провайдером, потому что учетные данные яблока можно использовать только один раз ... Поэтому на данный момент мне интересно, как мне выбраться из этой ситуации ation?

Если мы следуем документации, в которой говорится, чтобы аутентифицироваться с EmailAuthProviderID , но, очевидно, я думаю, что это всего лишь пример ... в документации я думаю, что вместо того, чтобы встретиться (как в моем случай) провайдер "apple.com" встретил EmailAuthProviderID ...


Я не прав с этой интерпретацией документации?

Я совершаю другие ошибки и не осознаю этого?

Может ли учетная запись Facebook не быть связана с существующей учетной записью Apple?


Извините, но я бился головой об этой проблеме за сутки ..

Это обновленный код

Auth.auth().signIn(with: FacebookAuthProvider.credential(withAccessToken: authToken)) { authResult, error in
    if error != nil {
      // Handle error.
        if (error as NSError?)?.code == AuthErrorCode.accountExistsWithDifferentCredential.rawValue {
            // Get pending credential and email of existing account.
            let existingAcctEmail = (error! as NSError).userInfo[AuthErrorUserInfoEmailKey] as! String
            let pendingCred = (error! as NSError).userInfo[AuthErrorUserInfoUpdatedCredentialKey] as! AuthCredential

            Auth.auth().fetchSignInMethods(forEmail: existingAcctEmail) { (methods, error) in

                if (methods?.contains("apple.com"))! {

                    // **** This Flow stops here because I can't reuse Apple's credentials a second time. *****

                    let tokenID = KeychainManager.getItemFromKeychain(forKey: AuthKey.applTokenID, keyPrefix: AuthKey.prefix)!
                    let nonce = KeychainManager.getItemFromKeychain(forKey: AuthKey.applNonce, keyPrefix: AuthKey.prefix)

                    let appleCredentials = OAuthProvider.credential(withProviderID: "apple.com", accessToken: tokenID)


                    Auth.auth().signIn(with: appleCredentials) { user, error in
                        if user != nil {
                          // Link pending credential to account.
                          Auth.auth().currentUser?.link(with: pendingCred) { result, error in
                            // ...
                            print("\n LINK")

                            }
                        }
                        else {
                            print(error!)

                        }
                    }
                }
            }
        }
    }

Ответы [ 2 ]

1 голос
/ 02 апреля 2020

Это решение, которое я принял .. Очевидно, оно должно быть настроено в соответствии с функционированием вашего приложения


До сих пор я решил попросить пользователя войти в систему в с существующим поставщиком. Когда пользователь входит в систему с существующим поставщиком , он создает соединение с выбранным поставщиком.

Я объясняю ...

Если пользователь хочет войти с помощью Apple и учетная запись на Google уже существует , код должен быть установлен таким образом, чтобы быть уверенным, что пользователь действительно хочет соединить двух провайдеров:

  1. в делегированном методе AutenticationService didCompleteWithAuthorization authorization: (если пользователь впервые входит в систему с яблоком ), проверьте с помощью Auth.auth (). FetchSignInMethods , какие провайдеры уже существуют с его электронной почтой, полученной из его информации appleID

Примечание :

Данные пользователя Apple предоставляются только на его первый доступ. Для всех других доступов с помощью входа в Apple данные пользователя не будут предоставлены


Если с электронной почтой пользователя нет провайдеров, выполните процедуру аутентификации

2.1 . Если существуют существующие провайдеры с таким же адресом электронной почты (например, GOOGLE ), в первую очередь мы показываем уведомление, информирующее пользователя о том, какие провайдеры связаны с его электронной почтой (предыдущие аутентификации с другими провайдерами) , тогда мы разрешаем пользователю выбирать, с каким провайдером ( в данном случае GOOGLE ) войти в систему для подключения Apple .

2.2 Когда пользователь выбирает провайдера ( GOOGLE ) для подключения Apple , мы вспоминаем поток аутентификации существующего провайдера (как если бы пользователь выдвигал GOOGLE button ).

На этом этапе с включенным потоком аутентификации (в нашем случае Google ) и если все успешно, мы вызываем Auth.auth (). CurrentUser.link (with: credential) с учетными данными Apple


Примечание:

Во всем этом учетные данные Apple были сохранены в связке ключей, когда пользователь нажал кнопка и удалена сразу после ссылка с существующим провайдером

Учетные данные Apple нельзя использовать повторно после использования


0 голосов
/ 02 апреля 2020

Итак, это решение, с которым я всегда работал, и оно упоминается в самой документации Firebase. Прежде всего, вам необходимо понять концепцию связывания Multiple Auth Providers. Я стараюсь быть максимально продуманным, так как многие из вас сталкиваются с этой проблемой. Firebase принадлежит Google, и каждый раз, когда вы входите с помощью Google, он автоматически переопределяет другие методы входа, такие как Apple Sign In, Facebook, Google.

Итак, для решения этой проблемы используйте кнопку Google Sign In. Я держу свой ответ в общих чертах c, даже разработчики Javascript могут следовать этому, как я делал то же самое в JS.

Вы должны получить email от Google и каждого провайдера, например FB, Apple et c. Затем вы должны использовать этот метод: fetchSignInMethods.

func fetchSignInMethods(provider: String, email: String) {
 Auth.auth().fetchSignInMethods(forEmail: email) { (providers, error) in
   if let error = error {
    print(error)
    return 
   } 

   let matched = providers?.filter{ $0 == provider }.count //Here I check if the provider I sent example `Facebook` and the fetched provider are same or not, if it is matched then I can go ahead and login the user directly else I have to show them linking alert as already this email exists with different sign in methods

  if providers?.isEmpty || matched == 1 {
            var credential: AuthCredential!

            switch provider {

            case Constants.LoginProviders.Google.rawValue: //google.com

                credential = GoogleAuthProvider.credential(withIDToken: idToken,accessToken: token) //idToken and AccessToken are fetched from Google Sign In Button

            default: //facebook.com

                credential = FacebookAuthProvider.credential(withAccessToken: token) //AccessToken is fetched from Facebook Sign In Button

                break
            }

            self.loginWithCredential(credentail: credential, provider: provider)
  } //I am also checking if provider is empty then I can sign them up for Email and Password users using Auth.auth().createUserWithEmailAndPassword 
  else {
      self.displayLinkingAlert(provider: fetchedProviderName)
  }
 } 
}

    //This function is used to display the alert when an account needs to be linked
//Provider is the parameter which will be fetched from LoginProviders(Constants.swift)

private func displayLinkingAlert(provider: String) {

     MKProgress.hide()

    let providerName = getProviderName(provider: provider)

    let alertC: UIAlertController = UIAlertController(title: "Link Accounts?", message: "This account is already linked with \(providerName). Do you want to link accounts?", preferredStyle: .alert)

    let linkAction: UIAlertAction = UIAlertAction(title: "Link account with \(providerName)", style: .default) { (_) in

        self.linkAccounts.toggle() //Toggle linkAccounts to true to link the accounts of user

        switch provider {

        case Constants.LoginProviders.Google.rawValue:

            GIDSignIn.sharedInstance()?.signIn()

            break

        default: self.facebookLoginWithPermissions(from: LoginViewController())

        }
    }

    let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)

    let actions: [UIAlertAction] = [linkAction, cancelAction]

    actions.forEach{ alertC.addAction($0) }

    UIApplication.topViewController()?.present(alertC, animated: true, completion: nil)

} //I also too a private var linkAccounts = false above in the class

//This function is used to get the providerName from the provider
//Ex.:- provider -> google.com, providerName: Google

private func getProviderName(provider: String) -> String
{
    var providerName = ""

    switch provider
    {
    case Constants.LoginProviders.Email.rawValue : providerName = "Email"

    case Constants.LoginProviders.Facebook.rawValue : providerName = "Facebook"

    case Constants.LoginProviders.Google.rawValue : providerName = "Google"

    default: break

    }

    return providerName
}

Теперь, это покажет вам предупреждение о связывании. Ну, это зависит от вас, как вы хотите продолжить. В моих приложениях включены методы входа в систему Google, Facebook и Email, и я показываю пользователям обоих из них случай, если два провайдера выбраны из Firebase. Например,

Если я уже зарегистрировался с Google и Facebook, то, если я попытаюсь зарегистрировать пользователя по электронной почте, он покажет мне две кнопки оповещения Link With Google и Link With Facebook. Теперь, если вы хотите, вы можете показать их обоих, или вы можете указать только одно, я могу даже добавить Apple Sign In здесь, это зависит от выбора каждого.

Теперь, когда появляется предупреждение и вы нажимаете опцию Link With Facebook, во-первых, toggle переменная linkAccount принимает значение true, а затем вызывается метод facebookTap, который снова fetch providers выглядит следующим образом:

private func facebookTap() {
   //Get the details and get the email then...

   fetchSignInMethods(provider: Constants.LoginProviders.Facebook.rawValue //facebook.com, email: emailFetched) //This will sign in the user using Facebook and as the linkAccount variable is true it will link ther user
}

//A common function used to login the users, by using their credentails
//Parameter credential is of type AuthCredential which is used to
//signInAndRetrieve data of a particular user.

func loginWithCredential(credentail: AuthCredential, provider: String) {

    authInstance.signInAndRetrieveData(with: credentail) { [unowned self](authResult, error) in

        if let error = error {
            print(error)
        } else {

            if self.linkAccounts {

                var loginCredential: AuthCredential!

                switch provider {
                case Constants.LoginProviders.Google.rawValue:
                    guard let facebookTokenString = AccessToken.current?.tokenString else { return }

                    loginCredential = FacebookAuthProvider.credential(withAccessToken: facebookTokenString)

                    break

                default:

                    loginCredential = GoogleAuthProvider.credential(withIDToken: self.googleIdToken, accessToken: self.googleAccessToken)

                    break
                }

                self.linkAccounts(credential: loginCredential)

            } else {

                self.getFirebaseToken()

            }
        }
    }
}

Теперь я использую это для link учетных записей:

//This function is used to link the Accounts
//Ex:- Google with Facebook, vice-versa

private func linkAccounts(credential: AuthCredential) {

    authInstance.currentUser?.link(with: credential, completion: { (authResult, error) in

        if let error = error {
            print(error)

            return

        } else {

            self.linkAccounts.toggle() //Toggle Link Account to false as acccounts are already linked

            //Navigate user to another page or do your stuff 

        }
    })

}

Теперь это решение, которое я пробовал, и оно работало для меня каждый раз. Он работал для меня в Swift, Flutter, Javascript и будет работать с любым другим языком. Вы можете сделать то же самое с Apple Sign In Button. В делегате, где вы получите подробную информацию, если пользователь поделился своим адресом электронной почты, вы можете использовать fetchSignInMethods, и он автоматически отобразится с указанным выше предупреждением. У меня нет возможности связать пользователя, который решил сохранить свою личность в секрете, но я скоро обновлю свой ответ.

Я старался быть максимально подробным, пожалуйста, дайте мне знать, если у вас есть какие-либо вопросы.

Once Email, Facebook and Google are linked

...