Vincula varios proveedores a una cuenta

En este documento, se muestra cómo vincular varios proveedores a una cuenta única de Identity Platform.

Identity Platform usa un ID único para identificar a los usuarios. Esto permite que los usuarios accedan a la misma cuenta con proveedores diferentes. Por ejemplo, un usuario que se registró inicialmente con un número de teléfono podría, luego, vincular su cuenta de Google y usar cualquiera de estos dos métodos para acceder.

Antes de comenzar

Agrega la compatibilidad con dos o más proveedores de identidad a tu app.

Habilita o inhabilita la vinculación de cuentas

La configuración de la vinculación de cuentas determina de qué forma Identity Platform maneja los usuarios que intentan acceder con el mismo correo electrónico mediante proveedores diferentes.

  • Vincula cuentas que usen el mismo correo electrónico: Identity Platform generará un error si un usuario intenta acceder con un correo electrónico que ya está en uso. Tu app puede detectar este error y vincular el proveedor nuevo con su cuenta existente.

  • Crea varias cuentas para cada proveedor de identidad: Se creará una cuenta de usuario nueva de Identity Platform cada vez que un usuario acceda con un proveedor diferente.

Para elegir una configuración, haz lo siguiente:

  1. Ve a la página Configuración de Identity Platform en la console de Google Cloud.

    Ir a la página Configuración

  2. Selecciona una configuración en Vinculación de cuentas de usuario.

  3. Haga clic en Save.

Vincula credenciales de proveedores federados

Para vincular credenciales de un proveedor federado, haz lo siguiente:

  1. Permite que el usuario acceda mediante cualquier proveedor o método de autenticación.

  2. Obtén el objeto del proveedor que corresponde al proveedor que deseas vincular con la cuenta del usuario. Por ejemplo:

    Web versión 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 versión 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. Pídele al usuario que acceda con el proveedor que deseas vincular. Puedes abrir una ventana emergente o redireccionar la página actual. El redireccionamiento es más sencillo para los usuarios de dispositivos móviles.

    Para mostrar una ventana emergente, llama a linkWithPopup():

    Web versión 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 versión 8

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

    Para redireccionar la página, primero llama a linkWithRedirect():

    Sigue las prácticas recomendadas cuando uses signInWithRedirect, linkWithRedirect o reauthenticateWithRedirect.

    Web versión 9

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

    Web versión 8

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

    Una vez que el usuario acceda, se lo redireccionará de vuelta a tu app. Luego, puedes recuperar el resultado del acceso si llamas a getRedirectResult():

    Web versión 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 versión 8

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

La cuenta del usuario con el proveedor federado ahora está vinculada con su cuenta de Identity Platform y puede usar el proveedor para acceder.

Vincula credenciales de correo electrónico y contraseña

Para agregar una dirección de correo electrónico y una contraseña a una cuenta de usuario existente, haz lo siguiente:

  1. Permite que el usuario acceda mediante cualquier método o proveedor de identidad.

  2. Pide al usuario que ingrese una dirección de correo electrónico y una contraseña.

  3. Crea un objeto AuthCredential con la dirección de correo electrónico y la contraseña:

    Web versión 9

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

    Web versión 8

    var credential = firebase.auth.EmailAuthProvider.credential(email, password);
  4. Pasa el objeto AuthCredential al método linkWithCredential() en el usuario que accedió:

    Web versión 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 versión 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);
      });

Las credenciales del correo electrónico y la contraseña ahora están vinculadas a la cuenta de Identity Platform del usuario y pueden usarlas para acceder.

Ten en cuenta que una credencial de proveedor federado se puede vincular a una cuenta de correo electrónico o contraseña con un correo electrónico diferente. Si esto ocurre, se puede usar el correo electrónico correspondiente al proveedor federado para crear una cuenta de correo electrónico o contraseña independiente.

Maneja el error de account-exists-with-different-credential

Si habilitaste la configuración Vincula cuentas que usan el mismo correo electrónico en la consola de Google Cloud, cuando un usuario intenta acceder a un proveedor (como SAML) con un correo electrónico que ya existe para otro proveedor (como Google), se muestra el error auth/account-exists-with-different-credential (junto con un objeto AuthCredential).

Para controlar este error, pídele al usuario que acceda con el proveedor existente. Luego, llama a linkWithCredential(), linkWithPopup() o linkWithRedirect() para asociar el proveedor nuevo a su cuenta con AuthCredential.

En el siguiente ejemplo, se muestra cómo controlar este error cuando un usuario intenta acceder mediante Facebook:

Web versión 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 versión 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();
      });
    });
  }
});

El uso de un redireccionamiento es similar al uso de una ventana emergente, excepto que tendrás que almacenar en caché la credencial pendiente entre los redireccionamientos de la página (por ejemplo, mediante el uso del almacenamiento de sesión).

Ten en cuenta que algunos proveedores, como Google y Microsoft, funcionan como proveedores de identidad social y de correo electrónico. Los proveedores de correo electrónico se consideran como autorizados para todas las direcciones relacionadas con su dominio de correo electrónico alojado. Esto significa que un usuario que accede con una dirección de correo electrónico alojada por el mismo proveedor nunca generará este error (por ejemplo, si se accede con Google mediante un correo electrónico @gmail.com o con Microsoft mediante un correo electrónico @live.com o @outlook.com).

Fusiona cuentas de forma manual

Si un usuario intenta acceder con credenciales que ya están vinculadas con otra cuenta de usuario mediante el mismo proveedor, fallarán los métodos integrados del SDK de cliente para la vinculación de cuentas. En esta situación, deberás fusionar las cuentas manualmente y, luego, borrar la segunda cuenta. Por ejemplo:

Web versión 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 versión 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);
}

Puedes desvincular un proveedor de la cuenta de un usuario. El usuario ya no podrá autenticarse con ese proveedor.

Para desvincular un proveedor, pasa el ID del proveedor al método unlink(). Puedes obtener los IDs de los proveedores de autenticación vinculados a un usuario desde la propiedad providerData.

Web versión 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 versión 8

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

¿Qué sigue?