將多個供應商連結至帳戶

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

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

事前準備

在應用程式中新增對多個身分識別提供者的支援。

啟用或停用帳戶連結

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

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

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

如要選擇設定,請按照下列步驟操作:

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

    前往「設定」頁面

  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 帳戶,使用者可以透過這些憑證登入帳戶。

請注意,同盟供應商憑證可以連結至電子郵件/密碼帳戶,但電子郵件地址必須不同。如果發生這種情況,您可以使用對應的同盟供應商電子郵件地址,建立獨立的電子郵件地址/密碼帳戶。

處理「帳戶已存在,但使用不同憑證」錯誤

如果您在Google Cloud 控制台中啟用「連結使用相同電子郵件的帳戶」設定,當使用者嘗試透過已用於其他供應商 (例如 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
  // ...
});

後續步驟