【问题标题】:Firebase JS API auth - account-exists-with-different-credentialFirebase JS API 身份验证 - 具有不同凭据的帐户存在
【发布时间】:2017-10-16 09:05:38
【问题描述】:

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

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

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

但是,如果用户:

  1. 用 Facebook 登录(没问题)
  2. 稍后,使用 Google 登录(也可以)
  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

谁能帮忙指出我们在这个实现中做错了什么?非常感谢。

【问题讨论】:

标签: javascript firebase firebase-authentication


【解决方案1】:

有时 Firebase 文档很棒,而有时它会让您想要更多。在这种情况下,在处理错误时,它在signInWithPopup 上给出了非常详细的说明。但是,signInWithRedirect 的全部说明是……

重定向模式

此错误在重定向模式中以类似的方式处理,不同之处在于必须在页面重定向之间缓存挂起的凭据(例如,使用会话存储)。

根据@bojeil 和@Dominic 的回答,您可以通过以下方式将Facebook 帐户与调用signInWithRedirect 的Google 帐户关联起来。

const providers = {
  google: new firebase.auth.GoogleAuthProvider(),
  facebook: new firebase.auth.FacebookAuthProvider(),
  twitter: new firebase.auth.TwitterAuthProvider(),
};

const handleAuthError = async (error) => {
  if (error.email && error.credential && error.code === 'auth/account-exists-with-different-credential') {
    // We need to retain access to the credential stored in `error.credential`
    // The docs suggest we use session storage, so we'll do that.

    sessionStorage.setItem('credential', JSON.stringify(error.credential));

    const signInMethods = await firebase.auth().fetchSignInMethodsForEmail(error.email); // -> ['google.com']
    const providerKey = signInMethods[0].split('.')[0]; // -> 'google'
    const provider = providers[providerKey]; // -> providers.google

    firebase.auth().signInWithRedirect(provider);
  }
};

const handleRedirect = async () => {
  try {
    const result = await firebase.auth().getRedirectResult();
    const savedCredential = sessionStorage.getItem('credential');

    // we found a saved credential in session storage
    if (result.user && savedCredential) {
      handleLinkAccounts(result.user, savedCredential);
    }
    return result;
  }
  catch (error) {
    handleAuthError(error);
  }
};

const handleLinkAccounts = (authUser, savedCredential) => {
  // Firebase has this little hidden gem of a method call fromJSON
  // You can use this method to parse the credential saved in session storage
  const token = firebase.auth.AuthCredential.fromJSON(savedCredential);

  const credential = firebase.auth.FacebookAuthProvider.credential(token);
  authUser.linkWithCredential(credential);

  // don't forget to remove the credential
  sessionStorage.removeItem('credential');
};

firebase.auth().onAuthStateChanged((authUser) => {
  handleRedirect();
});

【讨论】:

    【解决方案2】:

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

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

    }

    【讨论】:

    • 在本地存储上保存凭据?没办法:)
    【解决方案3】:

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

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

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

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

    【讨论】:

      【解决方案4】:

      我觉得 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 错误。
      • @PrintlnParams 此方法有效。如果您使用这种方法调用 signInWithPopup 并且仍然收到错误,则说明链中的某个地方出了问题。
      【解决方案5】:

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

      您的 sn-p 中的问题是您使用相同的凭据进行签名和链接。修改如下。 当您收到错误“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))
      

      【讨论】:

      • 太棒了!太感谢了。我没有完全理解“流程”,是我还是太复杂了?无论如何,现在一切都很好,非常感谢。
      • 我很高兴能帮上忙!这不是微不足道的,Firebase 文档应该更好地澄清这一点。
      • 使用 google 登录时如何获取 google provider 令牌?一个代码 sn-p 会很有帮助,THNX
      • link 已弃用,现在应使用 linkWithCredential。此外,signInWithCredential 不会返回用户,而是您必须从中获取用户的结果,因此应该这样做 result.user.linkWithCredential
      • 另外,fetchProvidersForEmail() 不是函数。你的意思是fetchSignInMethodsForEmail()
      【解决方案6】:

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

      this question

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

      【讨论】:

        猜你喜欢
        • 2021-12-19
        • 2021-05-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-08-04
        • 2021-12-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多