複数のプロバイダをアカウントにリンクする

この記事では、複数のプロバイダを 1 つの Identity Platform アカウントにリンクする方法について説明します。

Identity Platform は一意の ID を使用してユーザーを識別します。これにより、ユーザーは異なるプロバイダを使用して同じアカウントにログインできます。たとえば、最初に電話番号で登録したユーザーは、後で Google アカウントをリンクすると、どちらの方法でもログインできます。

始める前に

複数の ID プロバイダのサポートをアプリに追加する。

アカウントのリンクを有効または無効にする

アカウントのリンク設定により、異なるプロバイダを使用して同じメールアドレスでログインしようとした場合の Identity Platform の処理方法が決まります。

  • 同じメールアドレスを使用するアカウントをリンクする: Identity Platform では、使用中のメールアドレスを使用してユーザーがログインしようとすると、エラーが発生します。アプリでこのエラーを検出し、新しいプロバイダを既存のアカウントにリンクできます。

  • ID プロバイダごとに複数のアカウントを作成する: ユーザーが別のプロバイダでログインするたびに、新しい Identity Platform のユーザー アカウントが作成されます。

設定を選択するには:

  1. Google Cloud コンソールで Identity Platform の [設定] ページに移動します。

    [設定] ページに移動

  2. [ユーザー アカウントのリンク] で設定を選択します。

  3. [保存] をクリックします。

連携プロバイダの認証情報をリンクする

連携プロバイダから認証情報をリンクするには:

  1. 任意の認証プロバイダまたは認証メソッドでユーザーのログインを行います。

  2. ユーザーのアカウントにリンクするプロバイダに対応するプロバイダ オブジェクトを取得します。次に例を示します。

    Web バージョン 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();

    Web バージョン 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() を呼び出します。

    Web バージョン 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.
      // ...
    });

    Web バージョン 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 を使用する場合は、ベスト プラクティスに従ってください。

    Web バージョン 9

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

    Web バージョン 8

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

    ユーザーがログインすると、リダイレクトされてアプリに戻ります。その後、getRedirectResult() を呼び出してログイン結果を取得できます。

    Web バージョン 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.
      // ...
    });

    Web バージョン 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. 任意の ID プロバイダまたは ID メソッドでユーザーのログインを行います。

  2. ユーザーにメールアドレスとパスワードの入力を要求します。

  3. そのメールアドレスとパスワードを使って AuthCredential オブジェクトを作成します。

    Web バージョン 9

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

    Web バージョン 8

    var credential = firebase.auth.EmailAuthProvider.credential(email, password);
  4. ログインしているユーザーの linkWithCredential() メソッドに AuthCredential オブジェクトを渡します。

    Web バージョン 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);
      });

    Web バージョン 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 コンソールで [同じメールアドレスを使用するアカウントをリンク] 設定を有効にしている場合、ユーザーが、あるプロバイダ(Google など)用の既存のメールアドレスで、別のプロバイダ(SAML など)にログインしようとすると、エラー auth/account-exists-with-different-credential が(AuthCredential オブジェクトとともに)スローされます。

このエラーを処理するには、既存のプロバイダでログインするようユーザーに促します。次に、linkWithCredential()linkWithPopup()linkWithRedirect() のいずれかを呼び出して、AuthCredential を使用して新しいプロバイダをアカウントに関連付けます。

次の例は、ユーザーが Facebook を使用してログインしようとした際に発生した、このエラーを処理する方法を示しています。

Web バージョン 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();
      });
    });
  }
});

Web バージョン 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 などの一部のプロバイダは、メールとソーシャル ID プロバイダの両方としてサービスを提供している点に注意してください。メール プロバイダは、ホストされているメール ドメインに関連するすべてのアドレスに対して信頼できると見なされます。つまり、同じプロバイダによってホストされているメールアドレスでユーザーがログインした場合、このエラーは生成されません(たとえば、@gmail.com メールアドレスを使用して Google にログイン場合や、@live.com または @outlook.com を使用して Microsoft にログインする場合など)。

アカウントを手動で統合する

ユーザーが同じプロバイダを使用して別のユーザー アカウントとすでにリンクされた認証情報でログインしようとすると、アカウント リンクのための Client SDK の組み込みメソッドは失敗します。この場合は、アカウントを手動で統合してから、2 番目のアカウントを削除する必要があります。次に例を示します。

Web バージョン 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);
}

Web version 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() メソッドに渡します。ユーザーにリンクされている認証プロバイダのプロバイダ ID は providerData プロパティから取得できます。

Web バージョン 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
  // ...
});

Web バージョン 8

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

次のステップ