将多个提供商与一个帐号相关联

本文档介绍如何将多个提供商关联到单个 Identity Platform 帐号。

Identity Platform 使用唯一 ID 来标识用户。这样,用户便可以通过不同的提供商登录同一帐号。例如,最初使用电话号码注册的用户可以在之后关联其 Google 帐号,然后使用任一方法登录。

准备工作

为您的应用增加对两个或多个身份提供商的支持。

启用或停用帐号关联

帐号关联设置决定了 Identity Platform 如何处理尝试使用相同电子邮件通过不同提供商登录的用户。

  • 关联使用相同电子邮件地址的帐号:如果用户尝试使用已在使用的电子邮件地址登录,则 Identity Platform 会报错。您的应用可以捕获此错误,并将新的提供商关联到其现有帐号。

  • 为每个身份提供商创建多个帐号:每次用户通过不同的提供商登录时,系统都会创建一个新的 Identity Platform 用户帐号。

要选择设置,请执行以下操作:

  1. 转到 Cloud Console 中的 Identity Platform 设置页面。

    转到“设置”页面

  2. 选择用户帐号关联下的设置。

  3. 点击保存

关联联合提供商凭据

如需关联某个联合提供商的凭据,请执行以下操作:

  1. 使用任意身份验证提供商或方法登录用户。

  2. 获取要将其与用户帐号相关联的提供商所对应的提供商对象。例如:

    JavaScript

    var googleProvider = new firebase.auth.GoogleAuthProvider();
    var facebookProvider = new firebase.auth.FacebookAuthProvider();
    var twitterProvider = new firebase.auth.TwitterAuthProvider();
    var githubProvider = new firebase.auth.GithubAuthProvider();
  3. 提示用户使用要关联的提供商登录。您可以打开一个弹出式窗口,也可以重定向当前网页。对于移动设备用户而言,重定向更为便捷。

    如需显示弹出式窗口,请调用 linkWithPopup()

    JavaScript

    auth.currentUser.linkWithPopup(provider).then(function(result) {
      // Accounts successfully linked.
      var credential = result.credential;
      var user = result.user;
      // ...
    }).catch(function(error) {
      // Handle Errors here.
      // ...
    });

    如需重定向页面,请先调用 linkWithRedirect()

    JavaScript

    auth.currentUser.linkWithRedirect(provider)
      .then(/* ... */)
      .catch(/* ... */);

    用户登录后会被其重定向回您的应用。然后,您可以通过调用 getRedirectResult() 来检索登录结果:

    JavaScript

    auth.getRedirectResult().then(function(result) {
      if (result.credential) {
        // Accounts successfully linked.
        var credential = result.credential;
        var user = result.user;
        // ...
      }
    }).catch(function(error) {
      // Handle Errors here.
      // ...
    });

用户使用联合提供商的用户帐号现已关联至其 Identity Platform 帐号,用户可以通过该提供商登录。

关联电子邮件和密码凭据

要为现有用户帐号添加电子邮件地址和密码,请执行以下操作:

  1. 使用任意身份提供商或方法登录用户。

  2. 提示用户输入电子邮件地址和密码。

  3. 使用电子邮件地址和密码创建一个 AuthCredential 对象:

    JavaScript

    var credential = firebase.auth.EmailAuthProvider.credential(email, password);
  4. AuthCredential 对象传递给已登录用户的 linkWithCredential() 方法:

    JavaScript

    auth.currentUser.linkWithCredential(credential)
      .then(function(usercred) {
        var user = usercred.user;
        console.log("Account linking success", user);
      }).catch(function(error) {
        console.log("Account linking error", error);
      });

电子邮件和密码凭据现在已与用户的 Identity Platform 帐号关联,用户可以使用这些凭据进行登录。

处理“account-exists-with-different-credential”错误

如果您启用了关联使用相同电子邮件地址的帐号设置,当用户尝试使用已与现有账号关联的电子邮件通过提供商登录时,Identity Platform 将报错。

要处理此错误,请提示用户使用现有帐号登录。然后调用 linkWithCredential()linkWithPopup()linkWithRedirect() 将新的提供商关联至他们的帐号。

以下示例展示了如何在用户尝试通过 Facebook 登录时处理此错误:

JavaScript

// User tries to sign in with Facebook.
auth.signInWithPopup(new firebase.auth.FacebookAuthProvider()).catch(err => {
  // User's email already exists.
  if (err.code === 'auth/account-exists-with-different-credential') {
    // The pending Facebook credential.
    var pendingCred = err.credential;
    // The provider account's email address.
    var email = err.email;
    // Get the sign-in methods for this email.
    auth.fetchSignInMethodsForEmail(email).then(methods => {
      // If the user has several sign-in methods, the first method
      // in the list will be the "recommended" method to use.
      if (methods[0] === 'password') {
        // TODO: Ask the user for their password.
        // In real scenario, you should handle this asynchronously.
        var password = promptUserForPassword();
        auth.signInWithEmailAndPassword(email, password).then(result => {
          return result.user.linkWithCredential(pendingCred);
        }).then(() => {
          // Facebook account successfully linked to the existing user.
          goToApp();
        });
        return;
      }
      // All other cases are external providers.
      // Construct provider object for that provider.
      // TODO: Implement getProviderForProviderId.
      var provider = getProviderForProviderId(methods[0]);
      // At this point, you should let the user know that they already have an
      // account with a different provider, and validate they want to sign in
      // with the new provider.
      // Note: Browsers usually block popups triggered asynchronously, so in
      // real app, you should ask the user to click on a "Continue" button
      // that will trigger signInWithPopup().
      auth.signInWithPopup(provider).then(result => {
        // Note: Identity Platform doesn't control the provider's sign-in
        // flow, so it's possible for the user to sign in with an account
        // with a different email from the first one.

        // Link the Facebook credential. We have access to the pending
        // credential, so we can directly call the link method.
        result.user.linkWithCredential(pendingCred).then(usercred => {
          // Success.
          goToApp();
        });
      });
    });
  }
});

使用重定向与弹出式窗口类似,区别是您需要缓存在页面重定向之间待处理的凭据(例如,使用会话存储)。

请注意,一些提供商(如 Google 和 Microsoft)既是电子邮件服务提供商,也是社交身份提供商。电子邮件服务提供商相对其托管的电子邮件网域的所有相关地址而言具有权威性。这意味着使用同一提供商托管的电子邮件地址登录的用户绝不会引发此错误(例如,使用 @gmail.com 电子邮件通过 Google 登录,或使用 @live.com@outlook.com 电子邮件通过 Microsoft 登录)。

手动合并帐号

如果用户尝试使用已关联到其他用户帐号的凭据通过同一个提供商登录,则 Client SDK 的内置帐号关联方法将失败。在这种情况下,您需要手动合并帐号,然后删除第二个帐号。例如:

JavaScript

// Sign in first account.
const result1 = await auth.signInWithCredential(cred1);
const user1 = result1.user;
// Try to link a credential that belongs to an existing account
try {
  await user1.linkWithCredential(cred2);
} catch (error) {
  // cred2 already exists so an error is thrown.
  const result2 = await auth.signInWithCredential(error.credential);
  const user2 = result2.user;
  // Merge the data.
  mergeData(user1, user2);
  // Delete one of the accounts, and try again.
  await user2.delete();
  // Linking now will work.
  await user1.linkWithCredential(result2.credential);
}

您可以解除提供商与用户帐号的关联。用户将无法再通过该提供商进行身份验证。

要解除与提供商的关联,请将提供商 ID 传递给用户的 unlink() 方法。您可以从 providerData 属性中获取 ID。

JavaScript

user.unlink(providerId).then(function() {
  // Auth provider unlinked from account
  // ...
}).catch(function(error) {
  // An error happened
  // ...
});

后续步骤