將多個供應商連結至帳戶

本文將說明如何將多個供應者連結至單一 Identity Platform 帳戶。

Identity Platform 會使用專屬 ID 識別使用者。這樣一來,使用者就能透過不同的供應商登入同一個帳戶。舉例來說,如果使用者一開始是使用電話號碼註冊,之後可以連結 Google 帳戶,然後使用任一方法登入。

事前準備

在應用程式中新增對兩個以上識別資訊提供者的支援。

啟用或停用帳戶連結

帳戶連結設定會決定 Identity Platform 如何處理使用者嘗試使用不同供應商的相同電子郵件地址登入的情況。

  • 連結使用相同電子郵件地址的帳戶:如果使用者嘗試使用已在使用中的電子郵件地址登入,Identity Platform 會顯示錯誤訊息。您的應用程式可以擷取這項錯誤,並將新提供者連結至使用者的現有帳戶。

  • 為每個識別資訊提供者建立多個帳戶:每當使用者透過不同供應商登入時,系統就會建立新的 Identity Platform 使用者帳戶。

如何選擇設定:

  1. 前往Google Cloud 控制台的「Identity Platform」「Settings」(設定) 頁面。

    前往「設定」頁面

  2. 選取「使用者帳戶連結」下方的設定。

  3. 按一下 [儲存]

連結聯合供應商憑證

如要連結來自聯合供應商的憑證,請按照下列步驟操作:

  1. 使用任何驗證提供者或方法登入使用者。

  2. 取得與您要連結至使用者帳戶的供應商相對應的供應商物件。例如:

    網頁版 9

    import { GoogleAuthProvider, FacebookAuthProvider, TwitterAuthProvider, GithubAuthProvider } from "firebase/auth";
    
    const googleProvider = new GoogleAuthProvider();
    const facebookProvider = new FacebookAuthProvider();
    const twitterProvider = new TwitterAuthProvider();
    const githubProvider = new GithubAuthProvider();

    網頁版 8

    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()

    網頁版 9

    import { getAuth, linkWithPopup, GoogleAuthProvider } from "firebase/auth";
    const provider = new GoogleAuthProvider();
    
    const auth = getAuth();
    linkWithPopup(auth.currentUser, provider).then((result) => {
      // Accounts successfully linked.
      const credential = GoogleAuthProvider.credentialFromResult(result);
      const user = result.user;
      // ...
    }).catch((error) => {
      // Handle Errors here.
      // ...
    });

    網頁版 8

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

    如要重新導向網頁,請先呼叫 linkWithRedirect()

    使用 signInWithRedirectlinkWithRedirectreauthenticateWithRedirect 時,請遵循最佳做法

    網頁版 9

    import { getAuth, linkWithRedirect, GoogleAuthProvider } from "firebase/auth";
    const provider = new GoogleAuthProvider();
    
    const auth = getAuth();
    linkWithRedirect(auth.currentUser, provider)
      .then(/* ... */)
      .catch(/* ... */);

    網頁版 8

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

    使用者登入後,系統會將他們重新導向至您的應用程式。接著,您可以呼叫 getRedirectResult() 來擷取登入結果:

    網頁版 9

    import { getRedirectResult } from "firebase/auth";
    getRedirectResult(auth).then((result) => {
      const credential = GoogleAuthProvider.credentialFromResult(result);
      if (credential) {
        // Accounts successfully linked.
        const user = result.user;
        // ...
      }
    }).catch((error) => {
      // Handle Errors here.
      // ...
    });

    網頁版 8

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

使用者與聯合供應商的帳戶現在已連結至 Identity Platform 帳戶,因此可以使用供應商登入。

連結電子郵件地址和密碼憑證

如要為現有使用者帳戶新增電子郵件地址和密碼,請按照下列步驟操作:

  1. 使用任何身分識別資訊提供者或方法登入使用者。

  2. 提示使用者輸入電子郵件地址和密碼。

  3. 使用電子郵件地址和密碼建立 AuthCredential 物件:

    網頁版 9

    import { EmailAuthProvider } from "firebase/auth";
    
    const credential = EmailAuthProvider.credential(email, password);

    網頁版 8

    var credential = firebase.auth.EmailAuthProvider.credential(email, password);
  4. AuthCredential 物件傳遞至已登入使用者的 linkWithCredential() 方法:

    網頁版 9

    import { getAuth, linkWithCredential } from "firebase/auth";
    
    const auth = getAuth();
    linkWithCredential(auth.currentUser, credential)
      .then((usercred) => {
        const user = usercred.user;
        console.log("Account linking success", user);
      }).catch((error) => {
        console.log("Account linking error", error);
      });

    網頁版 8

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

電子郵件和密碼憑證現在已連結至使用者的 Identity Platform 帳戶,使用者可以使用這些憑證登入帳戶。

請注意,聯合式提供者憑證可以連結至使用不同電子郵件的電子郵件/密碼帳戶。發生這種情況時,您可以使用對應聯合服務供應商的電子郵件地址,建立個別的電子郵件/密碼帳戶。

處理「account-exists-with-different-credential」錯誤

如果您在Google Cloud 控制台中啟用「Link accounts that use the same email」設定,當使用者嘗試使用另一個供應商 (例如 Google) 的現有電子郵件地址登入供應商 (例如 SAML) 時,系統會擲回 auth/account-exists-with-different-credential 錯誤 (以及 AuthCredential 物件)。

如要處理這項錯誤,請提示使用者使用現有提供者登入。接著,請呼叫 linkWithCredential()linkWithPopup()linkWithRedirect(),使用 AuthCredential 將新供應商與其帳戶建立關聯。

以下範例說明如何在使用者嘗試使用 Facebook 登入時處理這項錯誤:

網頁版 9

  import { signInWithPopup, signInWithEmailAndPassword, linkWithCredential } from "firebase/auth";

  // User tries to sign in with Facebook.
  signInWithPopup(auth, facebookProvider).catch((error) => {
  // User's email already exists.
  if (error.code === 'auth/account-exists-with-different-credential') {
    // The pending Facebook credential.
    const pendingCred = error.credential;
    // The provider account's email address.
    const email = error.customData.email;

    // Present the user with a list of providers they might have
    // used to create the original account.
    // Then, ask the user to sign in with the existing provider.
    const method = promptUserForSignInMethod();

    if (method === 'password') {
      // TODO: Ask the user for their password.
      // In real scenario, you should handle this asynchronously.
      const password = promptUserForPassword();
      signInWithEmailAndPassword(auth, email, password).then((result) => {
        return linkWithCredential(result.user, 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.
    const provider = getProviderForProviderId(method);
    // 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().
    signInWithPopup(auth, 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.
      linkWithCredential(result.user, pendingCred).then((userCred) => {
        // Success.
        goToApp();
      });
    });
  }
});

網頁版 8

  // User tries to sign in with Facebook.
      auth.signInWithPopup(facebookProvider).catch((error) => {
  // User's email already exists.
  if (error.code === 'auth/account-exists-with-different-credential') {
    // The pending Facebook credential.
    const pendingCred = error.credential;
    // The provider account's email address.
    const email = error.email;

    // Present the user with a list of providers they might have
    // used to create the original account.
    // Then, ask the user to sign in with the existing provider.
    const method = promptUserForSignInMethod();

    if (method === 'password') {
      // TODO: Ask the user for their password.
      // In real scenario, you should handle this asynchronously.
      const 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.
    const provider = getProviderForProviderId(method);
    // 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)。

手動合併帳戶

如果使用者嘗試使用已連結至使用相同提供者的其他使用者帳戶的憑證登入,用戶端 SDK 內建的帳戶連結方法就會失敗。在這種情況下,您必須手動合併帳戶,然後刪除第二個帳戶。例如:

網頁版 9

// Sign in first account.
const result1 = await signInWithCredential(auth, cred1);
const user1 = result1.user;
// Try to link a credential that belongs to an existing account
try {
  await linkWithCredential(user1, cred2);
} catch (error) {
  // cred2 already exists so an error is thrown.
  const result2 = await signInWithCredential(auth, 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 linkWithCredential(user1, result2.credential);
}

網頁版 8

// 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。

網頁版 9

import { getAuth, unlink } from "firebase/auth";

const auth = getAuth();
unlink(auth.currentUser, providerId).then(() => {
  // Auth provider unlinked from account
  // ...
}).catch((error) => {
  // An error happened
  // ...
});

網頁版 8

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

後續步驟