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

ajo*_*nno 8 javascript firebase firebase-authentication

我们在尝试解决此问题时遇到了实际问题,因此希望获得一些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))
Run Code Online (Sandbox Code Playgroud)

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

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

boj*_*eil 13

发生的事情是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))
Run Code Online (Sandbox Code Playgroud)

  • 另外,“fetchProvidersForEmail()”不是一个函数。您的意思是“fetchSignInMethodsForEmail()”吗? (4认同)
  • 杰出的 !太感谢了。我没有完全理解“流程”,是我的问题还是它太复杂了?不管怎样,现在一切都很好,非常感谢。 (2认同)

Mar*_*ode 7

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

看到这个问题

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


Dom*_*nic 7

我觉得 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());
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,这个解决方案会导致弹出窗口被阻止。 (2认同)

Ram*_*man 6

@Bojeil 的答案是正确的,但我想为其添加一些颜色。

为什么 Firebase 会这样工作?

首先,为什么 Firebase 不直接处理这个问题而不让我参与?IOW,为什么我们auth/account-exists-with-different-credential首先会收到错误?

谷歌认为(正确或错误)Facebook(和其他)登录可能使用未经验证的电子邮件地址(请参阅此已关闭的问题等)。对于未经验证的电子邮件,此流程的安全问题如下:

  1. Alice在您的应用程序中使用alice@gmail.com创建帐户。
  2. Eve在其 Facebook 帐户中添加了alice@gmail.com作为未经验证的电子邮件地址。
  3. Eve使用自己的 Facebook 帐户和未经验证的alice@gmail.com电子邮件地址登录您的应用程序。
  4. Firebase在您的应用程序中将Eve 的Facebook 帐户附加到Alice 的帐户,并且 Eve 现在已经获得了对 Alice 数据的访问权限

因此,在第 4 步,Firebase 拒绝执行此操作,而是将auth/account-exists-with-different-credential失败返回给您的应用程序。您的应用程序现在要求 Alice 使用她的 Google 帐户登录(Eve 不能这样做),以便将 Facebook 帐户链接到与 Google 帐户相同的身份。

首先使用 Facebook 进行身份验证怎么样?

如果 Alice 首先使用她的 @gmail.com 帐户通过 Facebook 进行身份验证,她就可以通过 Facebook 访问她的帐户。如果她稍后通过 Google 使用相同的 @gmail.com 帐户进行身份验证,Firebase 会自动链接 Google 帐户并从帐户身份中删除之前的 Facebook 登录信息。

这是为了避免另一个攻击媒介:如果 Eve 可以在 Alice 登录之前通过她的 Facebook 帐户和 Alice 的电子邮件地址创建一个应用程序帐户,那么 Eve 就提前获得了对 Alice 帐户的访问权限。换句话说,Alice 可能会高兴地创建一个帐户/以 alice@gmail.com 身份登录,但没有意识到 Eve 的 Facebook 帐户已附加到该登录帐户,因此 Eve 可以登录该帐户。由于 Firebase 只是删除了非规范的 Facebook 登录信息,因此 Eve 无法再访问它,并且该攻击媒介也被消除了。

如何

同样,假设您想保留 Firebase“链接使用同一电子邮件的帐户”设置,@bojeil 的答案是正确的。但是,请注意,在浏览器环境中,通过弹出窗口获取规范凭据通常不起作用,因为第二个弹出窗口不会是用户操作的直接结果,浏览器将阻止它。

解决此问题的一种方法是通过重定向执行规范登录。另一种解决方案是向用户显示凭据问题,并显示一条消息,例如“您已经拥有与此电子邮件关联的 Gmail 帐户,单击以使用该帐户登录并确认此 Facebook 帐户的所有权”。然后,明确单击另一个按钮,以便使用该帐户的规范凭据登录。由于第二次登录现在是用户操作的直接结果,因此弹出窗口不会被阻止。