여러 공급업체를 하나의 계정에 연결

이 도움말에서는 여러 공급업체를 하나의 Identity Platform 계정에 연결하는 방법을 설명합니다.

Identity Platform은 고유 ID를 사용하여 사용자를 식별합니다. 이를 통해 사용자는 서로 다른 공급업체의 동일한 계정에 로그인할 수 있습니다. 예를 들어 처음에 전화번호로 등록한 사용자는 나중에 Google 계정을 연결한 다음 두 가지 방법 중 하나를 사용하여 로그인할 수 있습니다.

시작하기 전에

앱에 두 개 이상의 ID 공급업체에 대한 지원을 추가합니다.

계정 연결 사용 설정 또는 중지

계정 연결 설정에 따라 Identity Platform에서 서로 다른 공급업체를 통해 동일한 이메일로 로그인을 시도하는 사용자가 처리되는 방법이 결정됩니다.

  • 동일한 이메일을 사용하는 계정 연결: 사용자가 이미 사용 중인 이메일로 로그인을 시도하면 Identity Platform에서 오류가 발생합니다. 앱에서 이 오류를 발견하고 새로운 공급업체를 기존 계정에 연결할 수 있습니다.

  • 각 ID 공급업체마다 여러 계정 만들기: 사용자가 다른 공급업체로 로그인할 때마다 새로운 Identity Platform 사용자 계정이 생성됩니다.

설정을 선택하려면 다음 안내를 따르세요.

  1. Google Cloud Console에서 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()를 호출합니다.

    signInWithRedirect, linkWithRedirect, reauthenticateWithRedirect를 사용할 때는 권장사항을 따르세요.

    웹 버전 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. ID 공급업체 또는 메서드를 사용하여 사용자를 로그인 처리합니다.

  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. 로그인한 사용자의 linkWithCredential() 메서드에 AuthCredential 객체를 전달합니다.

    웹 버전 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 Console에서 동일한 이메일을 사용하는 계정 연결 설정을 사용 설정한 경우 사용자가 Google과 같은 다른 공급업체에 이미 있는 이메일로 SAML과 같은 공급업체에 로그인을 시도하면 AuthCredential 객체와 함께 auth/account-exists-with-different-credential 오류가 발생합니다.

이 오류를 처리하려면 사용자에게 기존 공급업체에 로그인하라는 메시지가 표시됩니다. 그러면 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와 같은 일부 공급업체는 이메일 및 소셜 ID 공급업체 역할도 합니다. 이메일 공급업체는 호스팅된 이메일 도메인과 관련된 모든 주소에 대한 권한이 있는 것으로 간주됩니다. 즉, 동일한 공급업체에서 호스팅하는 이메일 주소로 로그인하는 사용자에게는 이 오류가 발생하지 않습니다(예: @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() 메서드에 전달합니다. 사용자에 연결된 인증 제공업체의 제공업체 ID를 providerData 속성에서 가져올 수 있습니다.

웹 버전 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
  // ...
});

다음 단계