Firebase JS API 身份验证 - account-exists-with-different-credential

IT技术 javascript firebase firebase-authentication
2021-03-18 02:03:27

我们在尝试解决这个问题时遇到了真正的问题,所以希望得到一些 Firebase 帮助/那些已经解决了同样问题的人。

该应用程序是 React Native (0.43.2) 并使用 Firebase JS API(最新)

我们提供 Facebook 和 Google 身份验证。工作正常。

但是,如果用户:

  1. 使用 Facebook 登录(可以)
  2. 稍后,用谷歌登录(也可以)
  3. 稍后,尝试使用 Facebook 登录 - BOOM!不太好,Firebase 返回此错误:

auth/account-exists-with-different-credential

通过阅读文档和关于 SO 的一些帖子,我们认为以下内容是正确的,但显然不是,因为我们得到了相同的身份验证错误。

...error returned by Firebase auth after trying Facebook login...

const email = error.email;
const pendingCred = error.credential;

firebase.auth().fetchProvidersForEmail(email)
.then(providers => {
   //providers returns this array -> ["google.com"]
   firebase.auth().signInWithCredential(pendingCred)
   .then(result => {
       result.user.link(pendingCred)
   })
   .catch(error => log(error))

对 signInWithCredential 的调用抛出了同样的错误auth/account-exists-with-different-credential

任何人都可以帮助指出我们在这个实现中做错了什么吗?非常感激。

6个回答

发生的事情是 Firebase 对所有电子邮件强制使用相同的帐户。由于您已经拥有用于同一电子邮件的 Google 帐户,因此您需要将该 Facebook 帐户链接到 Google 帐户,以便用户可以访问相同的数据,并且下次能够使用 Google 或 Facebook 登录同一帐户。

您的代码段中的问题是您正在使用相同的凭据进行签名和链接。修改如下。当您收到错误“auth/account-exists-with-different-credential”时,该错误将包含 error.email 和 error.credential(Facebook OAuth 凭据)。您需要首先查找 error.email 以获取现有提供程序。

firebase.auth().fetchProvidersForEmail(error.email)
  .then(providers => {
    //providers returns this array -> ["google.com"]
    // You need to sign in the user to that google account
    // with the same email.
    // In a browser you can call:
    // var provider = new firebase.auth.GoogleAuthProvider();
    // provider.setCustomParameters({login_hint: error.email});
    // firebase.auth().signInWithPopup(provider)
    // If you have your own mechanism to get that token, you get it
    // for that Google email user and sign in
    firebase.auth().signInWithCredential(googleCred)
      .then(user => {
        // You can now link the pending credential from the first
        // error.
        user.linkWithCredential(error.credential)
      })
      .catch(error => log(error))
link已弃用,linkWithCredential现在应该使用此外,signInWithCredential它不会返回用户,而是返回您必须从中获取用户的结果,因此应该这样做result.user.linkWithCredential
2021-04-24 02:03:27
另外,fetchProvidersForEmail()不是函数。你的意思是fetchSignInMethodsForEmail()
2021-05-02 02:03:27
杰出的 !太感谢了。我没有完全理解“流程”,是我还是它太复杂了?无论如何,现在一切都很好,非常感谢。
2021-05-10 02:03:27
使用 google 登录时如何获取 google provider 令牌?一个代码片段会很有帮助,THNX
2021-05-12 02:03:27
我很高兴能帮上忙!这并非微不足道,Firebase 文档应该更好地阐明这一点。
2021-05-13 02:03:27

由于 google 是 @gmail.com 地址的可信提供商,因此它比使用 gmail 作为电子邮件的其他帐户具有更高的优先级。这就是为什么如果您使用 Facebook 登录然后 Gmail 不会引发错误,但如果您尝试将 Gmail 转到 Facebook,则会引发错误。

看到这个问题

如果您想允许多个帐户使用同一电子邮件,请转到 Firebase 控制台并在身份验证 -> 登录方法下,底部应该有一个选项可以切换此选项。

我觉得 Firebase 选择这种行为作为默认行为很奇怪也很不方便,而且解决方案很重要。这是截至撰写本文时基于 @bojeil 的回答的 Firebase 的完整且更新的解决方案。

function getProvider(providerId) {
  switch (providerId) {
    case firebase.auth.GoogleAuthProvider.PROVIDER_ID:
      return new firebase.auth.GoogleAuthProvider();
    case firebase.auth.FacebookAuthProvider.PROVIDER_ID:
      return new firebase.auth.FacebookAuthProvider();
    case firebase.auth.GithubAuthProvider.PROVIDER_ID:
      return new firebase.auth.GithubAuthProvider();
    default:
      throw new Error(`No provider implemented for ${providerId}`);
  }
}

const supportedPopupSignInMethods = [
  firebase.auth.GoogleAuthProvider.PROVIDER_ID,
  firebase.auth.FacebookAuthProvider.PROVIDER_ID,
  firebase.auth.GithubAuthProvider.PROVIDER_ID,
];

async function oauthLogin(provider) {
  try {
    await firebase.auth().signInWithPopup(provider);
  } catch (err) {
    if (err.email && err.credential && err.code === 'auth/account-exists-with-different-credential') {
      const providers = await firebase.auth().fetchSignInMethodsForEmail(err.email)
      const firstPopupProviderMethod = providers.find(p => supportedPopupSignInMethods.includes(p));

      // Test: Could this happen with email link then trying social provider?
      if (!firstPopupProviderMethod) {
        throw new Error(`Your account is linked to a provider that isn't supported.`);
      }

      const linkedProvider = getProvider(firstPopupProviderMethod);
      linkedProvider.setCustomParameters({ login_hint: err.email });

      const result = await firebase.auth().signInWithPopup(linkedProvider);
      result.user.linkWithCredential(err.credential);
    }

    // Handle errors...
    // toast.error(err.message || err.toString());
  }
}
这只会引发另一个auth/account-exists-with-different-credential错误。
2021-05-07 02:03:27
@PrintlnParams 这个方法有效。如果您signInWithPopup使用这种方法调用并仍然收到错误,则说明链中某处出现了其他错误。
2021-05-15 02:03:27
不幸的是,此解决方案导致弹出窗口被阻止。
2021-05-19 02:03:27

我已经写过如何在不需要再次登录的情况下执行此操作:

https://blog.wedport.co.uk/2020/05/29/react-native-firebase-auth-with-linking/

在关联帐户之前,您需要存储原始凭证并检索以静默登录。链接中的完整代码:

signInOrLink: async function (provider, credential, email) {
  this.saveCredential(provider, credential)
  await auth().signInWithCredential(credential).catch(
    async (error) => {
      try {
        if (error.code != "auth/account-exists-with-different-credential") {
          throw error;
        }
        let methods = await auth().fetchSignInMethodsForEmail(email);
        let oldCred = await this.getCredential(methods[0]);
        let prevUser = await auth().signInWithCredential(oldCred);
        auth().currentUser.linkWithCredential(credential);
      }
      catch (error) {
        throw error;
      }
    }
  );

}

在本地存储上保存凭据?没门 :)
2021-04-29 02:03:27

我给 Firebase 支持发了电子邮件,他们向我解释了更多。用他们自己的话说:

为了提供上下文,不同的电子邮件有自己的身份提供者。如果用户的电子邮件为 sample@gmail.com,则该电子邮件的 IDP(身份提供商)将是 Google,由域 @gmail.com 指定(对于域为 @gmail.com 的电子邮件,这不是真的) mycompany.com 或@yahoo.com)。

Firebase 身份验证允许在检测到使用的提供商是电子邮件的 IDP 时进行登录,无论他们是否使用“每个电子邮件地址一个帐户”设置并已使用以前的提供商(例如电子邮件/密码)登录基于身份验证或任何联合身份提供商,例如 Facebook。这意味着,如果他们使用电子邮件和密码登录 sample@gmail.com,然后是 Google(在每个电子邮件地址设置一个帐户下),Firebase 将允许后者,并且帐户的提供商将更新为 Google。这样做的原因是 IDP 很可能拥有有关电子邮件的最新信息。

另一方面,如果他们首先使用 Google 登录,然后使用具有相同关联电子邮件的电子邮件和密码帐户登录,我们不希望更新他们的 IDP 并将继续通知用户的默认行为那已经是与该电子邮件关联的帐户。