Autenticación con arquitectura multicliente

En este documento se explica cómo autenticar usuarios en un entorno de Identity Platform multicliente.

Antes de empezar

Asegúrate de que has habilitado la propiedad múltiple en tu proyecto y de que has configurado tus inquilinos. Consulta Primeros pasos con la arquitectura multicliente para saber cómo hacerlo.

También tendrás que añadir el SDK de cliente a tu aplicación:

  1. Ve a la página Identity Platform en la Google Cloud consola.
    Ve a la página de usuarios de Identity Platform

  2. En la parte superior derecha, haz clic en Detalles de la configuración de la aplicación.

  3. Copia el código en tu aplicación web. Por ejemplo:

Versión web 9

import { initializeApp } from "firebase/app";

const firebaseConfig = {
  apiKey: "...",
  // By default, authDomain is '[YOUR_APP].firebaseapp.com'.
  // You may replace it with a custom domain.
  authDomain: '[YOUR_CUSTOM_DOMAIN]'
};
const firebaseApp = initializeApp(firebaseConfig);

Versión web 8

firebase.initializeApp({
  apiKey: '...',
  // By default, authDomain is '[YOUR_APP].firebaseapp.com'.
  // You may replace it with a custom domain.
  authDomain: '[YOUR_CUSTOM_DOMAIN]'
});

Iniciar sesión con inquilinos

Para iniciar sesión en un cliente, el ID del cliente debe transferirse al objeto auth. Ten en cuenta que tenantId no se conserva cuando se vuelve a cargar la página.

Versión web 9

import { getAuth } from "firebase/auth";
const auth = getAuth();
const tenantId = "TENANT_ID1";
auth.tenantId = tenantId;

Versión web 8

const tenantId = "TENANT_ID1";
firebase.auth().tenantId = tenantId;

Cualquier solicitud de inicio de sesión futura de esta instancia de auth incluirá el ID de arrendatario (TENANT_ID1 en el ejemplo anterior) hasta que cambies o restablezcas el ID de arrendatario.

Puedes trabajar con varios inquilinos usando una o varias instancias de auth.

Para usar una sola instancia de auth, modifique la propiedad tenantId siempre que quiera cambiar de inquilino. Para volver a usar los IdPs a nivel de proyecto, asigna el valor null a tenantId:

Versión web 9

// One Auth instance
// Switch to tenant1
auth.tenantId = "TENANT_ID1";
// Switch to tenant2
auth.tenantId = "TENANT_ID2";
// Switch back to project level IdPs
auth.tenantId = null;

Versión web 8

// One Auth instance
// Switch to tenant1
firebase.auth().tenantId = "TENANT_ID1";
// Switch to tenant2
firebase.auth().tenantId = "TENANT_ID2";
// Switch back to project level IdPs
firebase.auth().tenantId = null;

Para usar varias instancias, cree una instancia auth para cada arrendatario y asígneles IDs diferentes:

Versión web 9

// Multiple Auth instances
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseApp1 = initializeApp(firebaseConfig1, 'app1_for_tenantId1');
const firebaseApp2 = initializeApp(firebaseConfig2, 'app2_for_tenantId2');

const auth1 = getAuth(firebaseApp1);
const auth2 = getAuth(firebaseApp2);

auth1.tenantId = "TENANT_ID1";
auth2.tenantId = "TENANT_ID2";

Versión web 8

// Multiple Auth instances
firebase.initializeApp(config, 'app1_for_tenantId1');
firebase.initializeApp(config, 'app2_for_tenantId2');

const auth1 = firebase.app('app1').auth();
const auth2 = firebase.app('app2').auth();

auth1.tenantId = "TENANT_ID1";
auth2.tenantId = "TENANT_ID2";

Después de iniciar sesión con un arrendatario, se devolverá un usuario del arrendatario con user.tenantId definido en ese arrendatario. Ten en cuenta que, si activas tenantId en la instancia tenantId más adelante, la propiedad currentUser no cambiará, sino que seguirá apuntando al mismo usuario que el inquilino anterior.auth

Versión web 9

import { signInWithEmailAndPassword, onAuthStateChanged } from "firebase/auth";
// Switch to TENANT_ID1
auth.tenantId = 'TENANT_ID1';

// Sign in with tenant
signInWithEmailAndPassword(auth, email, password)
  .then((userCredential) => {
    // User is signed in.
    const user = userCredential.user;
    // user.tenantId is set to 'TENANT_ID1'.
    // Switch to 'TENANT_ID2'.
    auth.tenantId = 'TENANT_ID2';
    // auth.currentUser still points to the user.
    // auth.currentUser.tenantId is 'TENANT_ID1'.
  });

// You could also get the current user from Auth state observer.
onAuthStateChanged(auth, (user) => {
  if (user) {
    // User is signed in.
    // user.tenantId is set to 'TENANT_ID1'.
  } else {
    // No user is signed in.
  }
});

Versión web 8

// Switch to TENANT_ID1
firebase.auth().tenantId = 'TENANT_ID1';

// Sign in with tenant
firebase.auth().signInWithEmailAndPassword(email, password)
  .then((result) => {
    const user = result.user;
    // user.tenantId is set to 'TENANT_ID1'.
    // Switch to 'TENANT_ID2'.
    firebase.auth().tenantId = 'TENANT_ID2';
    // firebase.auth().currentUser still point to the user.
    // firebase.auth().currentUser.tenantId is 'TENANT_ID1'.
  });

// You could also get the current user from Auth state observer.
firebase.auth().onAuthStateChanged((user) => {
  if (user) {
    // User is signed in.
    // user.tenantId is set to 'TENANT_ID1'.
  } else {
    // No user is signed in.
  }
});

Cuentas de correo y contraseña

En el siguiente ejemplo se muestra cómo registrar un nuevo usuario:

Versión web 9

import { createUserWithEmailAndPassword } from "firebase/auth";
auth.tenantId = 'TENANT_ID';

createUserWithEmailAndPassword(auth, email, password)
  .then((userCredential) => {
    // User is signed in.
    // userCredential.user.tenantId is 'TENANT_ID'.
  }).catch((error) => {
    // Handle / display error.
    // ...
  });

Versión web 8

firebase.auth().tenantId = 'TENANT_ID';

firebase.auth().createUserWithEmailAndPassword(email, password)
  .then((result) => {
    // result.user.tenantId is 'TENANT_ID'.
  }).catch((error) => {
    // Handle error.
  });

Para iniciar sesión con una cuenta ya creada, sigue estos pasos:

Versión web 9

import { signInWithEmailAndPassword } from "firebase/auth";
auth.tenantId = 'TENANT_ID';

signInWithEmailAndPassword(auth, email, password)
  .then((userCredential) => {
    // User is signed in.
    // userCredential.user.tenantId is 'TENANT_ID'.
  }).catch((error) => {
    // Handle / display error.
    // ...
  });

Versión web 8

firebase.auth().tenantId = 'TENANT_ID';

firebase.auth().signInWithEmailAndPassword(email, password)
  .then((result) => {
    // result.user.tenantId is 'TENANT_ID'.
  }).catch((error) => {
    // Handle error.
  });

SAML

Para iniciar sesión con un proveedor de SAML, crea una instancia de SAMLAuthProvider con el ID del proveedor de la consola: Google Cloud

Versión web 9

import { SAMLAuthProvider } from "firebase/auth";

const provider = new SAMLAuthProvider("saml.myProvider");

Versión web 8

const provider = new firebase.auth.SAMLAuthProvider('saml.myProvider');

A continuación, puedes usar un flujo emergente o de redirección para iniciar sesión en el proveedor de SAML.

  • Ventana emergente

    Versión web 9

    import { signInWithPopup } from "firebase/auth";
    // Switch to TENANT_ID1.
    auth.tenantId = 'TENANT_ID1';
    
    // Sign-in with popup.
    signInWithPopup(auth, provider)
      .then((userCredential) => {
        // User is signed in.
        const user = userCredential.user;
        // user.tenantId is set to 'TENANT_ID1'.
        // Provider data available from the result.user.getIdToken()
        // or from result.user.providerData
      })
      .catch((error) => {
        // Handle / display error.
        // ...
      });

    Versión web 8

    // Switch to TENANT_ID1.
    firebase.auth().tenantId = 'TENANT_ID1';
    
    // Sign-in with popup.
    firebase.auth().signInWithPopup(provider)
      .then((result) => {
        // User is signed in.
        // tenant ID is available in result.user.tenantId.
        // Identity provider data is available in result.additionalUserInfo.profile.
      })
      .catch((error) => {
        // Handle error.
      });

  • Redirigir

    Versión web 9

    import { signInWithRedirect, getRedirectResult } from "firebase/auth";
    // Switch to TENANT_ID1.
    auth.tenantId = 'TENANT_ID1';
    
    // Sign-in with redirect.
    signInWithRedirect(auth, provider);
    
    // After the user completes sign-in and returns to the app, you can get
    // the sign-in result by calling getRedirectResult. However, if they sign out
    // and sign in again with an IdP, no tenant is used.
    getRedirectResult(auth)
      .then((result) => {
        // User is signed in.
        // The tenant ID available in result.user.tenantId.
        // Provider data available from the result.user.getIdToken()
        // or from result.user.providerData
      })
      .catch((error) => {
        // Handle / display error.
        // ...
      });

    Versión web 8

    // Switch to TENANT_ID1.
    firebase.auth().tenantId = 'TENANT_ID1';
    
    // Sign-in with redirect.
    firebase.auth().signInWithRedirect(provider);
    
    // After the user completes sign-in and returns to the app, you can get
    // the sign-in result by calling getRedirectResult. However, if they sign out
    // and sign in again with an IdP, no tenant is used.
    firebase.auth().getRedirectResult()
      .then((result) => {
        // User is signed in.
        // The tenant ID available in result.user.tenantId.
        // Identity provider data is available in result.additionalUserInfo.profile.
      })
      .catch((error) => {
        // Handle error.
      });

En ambos casos, asegúrate de definir el ID de arrendatario correcto en la instancia de auth.

Para iniciar el flujo de autenticación, muestra una interfaz que pida al usuario que proporcione su dirección de correo y, a continuación, llama a sendSignInLinkToEmail para enviarle un enlace de autenticación. Asegúrate de definir el ID de arrendatario correcto en la instancia auth antes de enviar el correo.

Versión web 9

import { sendSignInLinkToEmail } from "firebase/auth";
// Switch to TENANT_ID1
auth.tenantId = 'TENANT_ID1';

sendSignInLinkToEmail(auth, email, actionCodeSettings)
  .then(() => {
    // The link was successfully sent. Inform the user.
    // Save the email locally so you don't need to ask the user for it again
    // if they open the link on the same device.
    window.localStorage.setItem('emailForSignIn', email);
  })
  .catch((error) => {
    // Handle / display error.
    // ...
  });

Versión web 8

// Switch to TENANT_ID1
firebase.auth().tenantId = 'TENANT_ID1';

firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings)
  .then(() => {
    // The link was successfully sent. Inform the user.
    // Save the email locally so you don't need to ask the user for it again
    // if they open the link on the same device.
    window.localStorage.setItem('emailForSignIn', email);
  })
  .catch((error) => {
    // Some error occurred, you can inspect the code: error.code
  });

Para completar el inicio de sesión en la página de destino, primero analiza el ID de arrendatario del enlace del correo y configúralo en la instancia auth. A continuación, llama a signInWithEmailLink con el correo del usuario y el enlace de correo real que contiene el código de un solo uso.

Versión web 9

import { isSignInWithEmailLink, parseActionCodeURL, signInWithEmailLink } from "firebase/auth";
if (isSignInWithEmailLink(auth, window.location.href)) {
  const actionCodeUrl = parseActionCodeURL(window.location.href);
  if (actionCodeUrl.tenantId) {
    auth.tenantId = actionCodeUrl.tenantId;
  }
  let email = window.localStorage.getItem('emailForSignIn');
  if (!email) {
    // User opened the link on a different device. To prevent session fixation
    // attacks, ask the user to provide the associated email again. For example:
    email = window.prompt('Please provide your email for confirmation');
  }
  // The client SDK will parse the code from the link for you.
  signInWithEmailLink(auth, email, window.location.href)
    .then((result) => {
      // User is signed in.
      // tenant ID available in result.user.tenantId.
      // Clear email from storage.
      window.localStorage.removeItem('emailForSignIn');
    });
}

Versión web 8

if (firebase.auth().isSignInWithEmailLink(window.location.href)) {
  const actionCodeUrl = firebase.auth.ActionCodeURL.parseLink(window.location.href);
  if (actionCodeUrl.tenantId) {
    firebase.auth().tenantId = actionCodeUrl.tenantId;
  }
  let email = window.localStorage.getItem('emailForSignIn');
  if (!email) {
    // User opened the link on a different device. To prevent session fixation
    // attacks, ask the user to provide the associated email again. For example:
    email = window.prompt('Please provide your email for confirmation');
  }
  firebase.auth().signInWithEmailLink(email, window.location.href)
    .then((result) => {
      // User is signed in.
      // tenant ID available in result.user.tenantId.
    });
}

Crear tokens personalizados

Crear un token personalizado compatible con varios clientes es idéntico a crear un token personalizado normal. Siempre que se haya definido el ID de cliente correcto en la instancia de auth, se añadirá una reclamación de nivel superior tenant_id al JWT resultante. Consulta Crear tokens personalizados para obtener instrucciones detalladas sobre cómo crear y usar tokens personalizados.

En el siguiente ejemplo se muestra cómo crear un token personalizado con el SDK de administrador:

Versión web 9

// Ensure you're using a tenant-aware auth instance
const tenantManager = admin.auth().tenantManager();
const tenantAuth = tenantManager.authForTenant('TENANT_ID1');

// Create a custom token in the usual manner
tenantAuth.createCustomToken(uid)
  .then((customToken) => {
    // Send token back to client
  })
  .catch((error) => {
    console.log('Error creating custom token:', error);
  });

Versión web 8

// Ensure you're using a tenant-aware auth instance
const tenantManager = admin.auth().tenantManager();
const tenantAuth = tenantManager.authForTenant('TENANT_ID1');

// Create a custom token in the usual manner
tenantAuth.createCustomToken(uid)
  .then((customToken) => {
    // Send token back to client
  })
  .catch((error) => {
    console.log('Error creating custom token:', error);
  });

En el siguiente código se muestra cómo iniciar sesión con un token personalizado:

Versión web 9

import { signInWithCustomToken } from "firebase/auth";
auth.tenantId = 'TENANT_ID1';

signInWithCustomToken(auth, token)
  .catch((error) => {
    // Handle / display error.
    // ...
  });

Versión web 8

firebase.auth().tenantId = 'TENANT_ID1';

firebase.auth().signInWithCustomToken(token)
  .catch((error) => {
    // Handle Errors here.
    const errorCode = error.code;
    const errorMessage = error.message;
    // ...
  });

Ten en cuenta que, si los IDs de los arrendatarios no coinciden, el método signInWithCustomToken() fallará.

Vincular credenciales de usuario de varios arrendatarios

Puedes vincular otros tipos de credenciales a un usuario multitenant. Por ejemplo, si un usuario se ha autenticado previamente con un proveedor de SAML en un inquilino, puedes añadir el inicio de sesión con correo y contraseña a su cuenta para que pueda usar cualquiera de los dos métodos para iniciar sesión en el inquilino.

Versión web 9

import { signInWithPopup, EmailAuthProvider, linkWithCredential, SAMLAuthProvider, signInWithCredential } from "firebase/auth";
// Switch to TENANT_ID1
auth.tenantId = 'TENANT_ID1';

// Sign-in with popup
signInWithPopup(auth, provider)
  .then((userCredential) => {
    // Existing user with e.g. SAML provider.
    const prevUser = userCredential.user;
    const emailCredential =
      EmailAuthProvider.credential(email, password);
    return linkWithCredential(prevUser, emailCredential)
      .then((linkResult) => {
        // Sign in with the newly linked credential
        const linkCredential = SAMLAuthProvider.credentialFromResult(linkResult);
        return signInWithCredential(auth, linkCredential);
      })
      .then((signInResult) => {
        // Handle sign in of merged user
        // ...
      });
  })
  .catch((error) => {
    // Handle / display error.
    // ...
  });

Versión web 8

// Switch to TENANT_ID1
firebase.auth().tenantId = 'TENANT_ID1';

// Sign-in with popup
firebase.auth().signInWithPopup(provider)
  .then((result) => {
    // Existing user with e.g. SAML provider.
    const user = result.user;
    const emailCredential =
      firebase.auth.EmailAuthProvider.credential(email, password);
    return user.linkWithCredential(emailCredential);
  })
  .then((linkResult) => {
    // The user can sign in with both SAML and email/password now.
  });

Al vincular o volver a autenticar a un usuario multitenant, se ignorará auth.tenantId. Usa user.tenantId para especificar qué cliente quieres usar. Esto también se aplica a otras APIs de gestión de usuarios, como updateProfile y updatePassword.

Gestionar los errores de cuenta existente con otra credencial

Si has habilitado el ajuste Vincular cuentas que usan el mismo correo en la consolaGoogle Cloud , cuando un usuario intente iniciar sesión en un proveedor (como SAML) con un correo que ya exista en otro proveedor (como Google), se mostrará el error auth/account-exists-with-different-credential (junto con un objeto AuthCredential).

Para terminar de iniciar sesión con el proveedor que quiere, el usuario debe iniciar sesión primero en el proveedor que ya tiene (Google) y, a continuación, vincularse al otro AuthCredential(SAML).

Puedes usar un flujo emergente o de redirección para gestionar este error.

  • Ventana emergente

    Versión web 9

    import { signInWithPopup, fetchSignInMethodsForEmail, linkWithCredential } from "firebase/auth";
    // Step 1.
    // User tries to sign in to the SAML provider in that tenant.
    auth.tenantId = 'TENANT_ID';
    signInWithPopup(auth, samlProvider)
      .catch((error) => {
        // An error happened.
        if (error.code === 'auth/account-exists-with-different-credential') {
          // Step 2.
          // User's email already exists.
          // The pending SAML credential.
          const pendingCred = error.credential;
          // The credential's tenantId if needed: error.tenantId
          // The provider account's email address.
          const email = error.customData.email;
          // Get sign-in methods for this email.
          fetchSignInMethodsForEmail(email, auth)
            .then((methods) => {
              // Step 3.
              // Ask the user to sign in with existing Google account.
              if (methods[0] == 'google.com') {
                signInWithPopup(auth, googleProvider)
                  .then((result) => {
                    // Step 4
                    // Link the SAML AuthCredential to the existing user.
                    linkWithCredential(result.user, pendingCred)
                      .then((linkResult) => {
                        // SAML account successfully linked to the existing
                        // user.
                        goToApp();
                      });
                  });
              }
            });
        }
      });

    Versión web 8

    // Step 1.
    // User tries to sign in to the SAML provider in that tenant.
    firebase.auth().tenantId = 'TENANT_ID';
    firebase.auth().signInWithPopup(samlProvider)
      .catch((error) => {
        // An error happened.
        if (error.code === 'auth/account-exists-with-different-credential') {
          // Step 2.
          // User's email already exists.
          // The pending SAML credential.
          const pendingCred = error.credential;
          // The credential's tenantId if needed: error.tenantId
          // The provider account's email address.
          const email = error.email;
          // Get sign-in methods for this email.
          firebase.auth().fetchSignInMethodsForEmail(email)
            .then((methods) => {
              // Step 3.
              // Ask the user to sign in with existing Google account.
              if (methods[0] == 'google.com') {
                firebase.auth().signInWithPopup(googleProvider)
                  .then((result) => {
                    // Step 4
                    // Link the SAML AuthCredential to the existing user.
                    result.user.linkWithCredential(pendingCred)
                      .then((linkResult) => {
                        // SAML account successfully linked to the existing
                        // user.
                        goToApp();
                      });
                  });
              }
            });
        }
      });
  • Redirigir

    Cuando se usa signInWithRedirect, auth/account-exists-with-different-credential se producirán errores en getRedirectResult al finalizar el flujo de redirección.

    El objeto de error contiene la propiedad error.tenantId. Como el ID de arrendatario de la instancia auth no se conserva después de la redirección, tendrás que definir el ID de arrendatario del objeto de error en la instancia auth.

    En el siguiente ejemplo se muestra cómo gestionar el error:

    Versión web 9

    import { signInWithRedirect, getRedirectResult, fetchSignInMethodsForEmail, linkWithCredential } from "firebase/auth";
    // Step 1.
    // User tries to sign in to SAML provider.
    auth.tenantId = 'TENANT_ID';
    signInWithRedirect(auth, samlProvider);
    var pendingCred;
    // Redirect back from SAML IDP. auth.tenantId is null after redirecting.
    getRedirectResult(auth).catch((error) => {
      if (error.code === 'auth/account-exists-with-different-credential') {
        // Step 2.
        // User's email already exists.
        const tenantId = error.tenantId;
        // The pending SAML credential.
        pendingCred = error.credential;
        // The provider account's email address.
        const email = error.customData.email;
        // Need to set the tenant ID again as the page was reloaded and the
        // previous setting was reset.
        auth.tenantId = tenantId;
        // Get sign-in methods for this email.
        fetchSignInMethodsForEmail(auth, email)
          .then((methods) => {
            // Step 3.
            // Ask the user to sign in with existing Google account.
            if (methods[0] == 'google.com') {
              signInWithRedirect(auth, googleProvider);
            }
          });
      }
    });
    
    // Redirect back from Google. auth.tenantId is null after redirecting.
    getRedirectResult(auth).then((result) => {
      // Step 4
      // Link the SAML AuthCredential to the existing user.
      // result.user.tenantId is 'TENANT_ID'.
      linkWithCredential(result.user, pendingCred)
        .then((linkResult) => {
          // SAML account successfully linked to the existing
          // user.
          goToApp();
        });
    });

    Versión web 8

    // Step 1.
    // User tries to sign in to SAML provider.
    firebase.auth().tenantId = 'TENANT_ID';
    firebase.auth().signInWithRedirect(samlProvider);
    var pendingCred;
    // Redirect back from SAML IDP. auth.tenantId is null after redirecting.
    firebase.auth().getRedirectResult().catch((error) => {
      if (error.code === 'auth/account-exists-with-different-credential') {
        // Step 2.
        // User's email already exists.
        const tenantId = error.tenantId;
        // The pending SAML credential.
        pendingCred = error.credential;
        // The provider account's email address.
        const email = error.email;
        // Need to set the tenant ID again as the page was reloaded and the
        // previous setting was reset.
        firebase.auth().tenantId = tenantId;
        // Get sign-in methods for this email.
        firebase.auth().fetchSignInMethodsForEmail(email)
          .then((methods) => {
            // Step 3.
            // Ask the user to sign in with existing Google account.
            if (methods[0] == 'google.com') {
              firebase.auth().signInWithRedirect(googleProvider);
            }
          });
      }
    });
    
    // Redirect back from Google. auth.tenantId is null after redirecting.
    firebase.auth().getRedirectResult().then((result) => {
      // Step 4
      // Link the SAML AuthCredential to the existing user.
      // result.user.tenantId is 'TENANT_ID'.
      result.user.linkWithCredential(pendingCred)
        .then((linkResult) => {
          // SAML account successfully linked to the existing
          // user.
          goToApp();
        });
    });

Inhabilitar la creación y eliminación de cuentas de usuario final

Hay situaciones en las que quieres que los administradores creen cuentas de usuario en lugar de que se creen mediante acciones de los usuarios. En estos casos, puedes inhabilitar las acciones de los usuarios a través de nuestra API REST:

curl --location --request PATCH 'https://identitytoolkit.googleapis.com/v2/projects/PROJECT_ID/tenants/TENANT_ID?updateMask=client' \
  --header 'Authorization: Bearer AUTH_TOKEN' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "client": {
        "permissions": {
            "disabled_user_signup": true,
            "disabled_user_deletion": true
        }
    }
}'

Haz los cambios siguientes:

  • AUTH_TOKEN: el token de autenticación.
  • PROJECT_ID: el ID del proyecto.
  • TENANT_ID: el ID de cliente.

Siguientes pasos