Javascript Firebase JS API验证-存在具有不同凭据的帐户

Javascript Firebase JS API验证-存在具有不同凭据的帐户,javascript,firebase,firebase-authentication,Javascript,Firebase,Firebase Authentication,我们在试图解决这个问题时遇到了实际问题,因此希望得到Firebase的帮助/那些解决了同样问题的人 该应用程序是React本机(0.43.2)并使用Firebase JS API(最新版本) 我们提供Facebook和谷歌认证。很好 但是,如果用户: 使用Facebook登录(可以) 稍后,登录谷歌(也可以) 稍后,尝试登录Facebook-BOOM!不太好,Firebase返回以下错误: auth/帐户使用不同的凭据存在 通过阅读文档和一些文章,我们认为以下内容是正确的,但显然不是,因为我们得

我们在试图解决这个问题时遇到了实际问题,因此希望得到Firebase的帮助/那些解决了同样问题的人

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

我们提供Facebook和谷歌认证。很好

但是,如果用户:

  • 使用Facebook登录(可以)
  • 稍后,登录谷歌(也可以)
  • 稍后,尝试登录Facebook-BOOM!不太好,Firebase返回以下错误:
  • auth/帐户使用不同的凭据存在

    通过阅读文档和一些文章,我们认为以下内容是正确的,但显然不是,因为我们得到了相同的auth错误

    ...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/帐户存在于不同的凭据中


    有人能帮我们指出这个实现的错误吗?非常感谢。

    由于谷歌是@gmail.com地址的可信提供商,它比其他使用gmail作为电子邮件的帐户具有更高的优先级。这就是为什么如果你登录Facebook,Gmail不会抛出一个错误,但是如果你尝试登录Facebook,Gmail会抛出一个错误


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

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

    代码段中的问题是您正在使用相同的凭据进行签名和链接。修改如下。 当您收到错误“身份验证/帐户使用不同凭据存在”时, 错误将包含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选择这种行为作为默认行为既奇怪又不方便,而且解决方案非常简单。以下是截至本文撰写之时,基于@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());
      }
    }
    

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

    为了提供上下文,不同的电子邮件都有自己的身份提供者。如果用户的电子邮件为sample@gmail.com,该电子邮件的IDP(身份提供者)将是谷歌,由域名@gmail.com指定(域名为@mycompany.com或@yahoo.com的电子邮件不属于这种情况)

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

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


    我在这里写过如何在不需要第二次登录的情况下执行此操作:

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

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

    }

    有时候firebase文档很棒,有时候它会让您想要更多。在这种情况下,当涉及到处理错误时,它会给出关于
    使用弹出菜单登录的非常详细的说明。但是,
    带重定向的signInWithRedirect
    的全部说明是

    重定向模式

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

    根据@bojeil和@Dominic的回答,以下是如何将facebook帐户与调用
    signInWithRedirect
    的google帐户链接

    const提供程序={
    google:new firebase.auth.GoogleAuthProvider(),
    facebook:new firebase.auth.FacebookAuthProvider(),
    twitter:new firebase.auth.TwitterAuthProvider(),
    };
    const handleAuthError=异步(错误)=>{
    if(error.email&&error.credential&&error.code===='auth/account以不同的凭据存在'){
    //我们需要保留对存储在'error.credential'中的凭据的访问权限`
    //文档建议我们使用会话存储,因此我们将这样做。
    setItem('credential',JSON.stringify(error.credential));
    const signingmethods=wait firebase.auth().fetchsigningmethodsforemail(error.email);//->['google.com']
    const providerKey=signingmethods[0]。拆分('.')[0];/->“谷歌”
    const provider=providers[providerKey];//->providers.google
    firebase.auth().signInWithRedirect(提供程序);
    }
    };
    const handleRedirect=async()=>{
    试一试{
    const result=await firebase.auth().getRedirectResult();
    const savedCredential=sessionStorage.getItem('credential');
    //我们在会话存储中找到保存的凭据
    if(result.user&&savedCredential){
    handleLinkAccounts(result.user,savedCredential);
    }
    返回结果;
    }
    捕获(错误){
    handleAuthError(错误);
    }
    };
    const handleLinkAccounts=(authUser,savedCredential)=>{
    //火基h