Firebase JS API auth - account-exists-with-different-credential

时间:2017-05-17 04:41:18

标签: javascript firebase firebase-authentication

我们在尝试解决此问题时遇到了实际问题,因此希望获得一些Firebase帮助/解决了同样问题的帮助。

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

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

但是,如果是用户:

  1. 使用Facebook登录(没问题)
  2. 之后,与Google签约(也没关系)
  3. 之后,尝试使用Facebook登录 - BOOM!不太好,Firebase会返回此错误:
  4. 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 个答案:

答案 0 :(得分:12)

Firebase为所有电子邮件强制执行相同的帐户。由于您已经拥有同一封电子邮件的Google帐户,因此您需要将该Facebook帐户与Google帐户相关联,以便用户可以访问相同的数据,并且下次可以使用Google或Facebook登录同一帐户。< / p>

您的代码段中的问题是您使用相同的凭据进行签名和链接。修改如下。 当您收到错误&#39; auth / account-exists-with-different-credential&#39;时, 该错误将包含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))

答案 1 :(得分:2)

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

请参阅this question

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

答案 2 :(得分: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 :(得分: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();
});

答案 4 :(得分:0)

我发现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());
  }
}

答案 5 :(得分:0)

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

  

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

     

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

     

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