Autenticazione con multitenancy

Questo documento mostra come autenticare gli utenti in un ambiente multi-tenant Identity Platform.

Prima di iniziare

Assicurati di aver abilitato la multitenancy per il tuo progetto e configurato i tenant. Consulta la Guida introduttiva all'architettura multitenancy per ulteriori informazioni.

Dovrai inoltre aggiungere l'SDK del client alla tua app:

  1. Vai alla pagina Identity Platform nella console Google Cloud.
    Vai alla pagina Utenti di Identity Platform

  2. In alto a destra, fai clic su Dettagli di configurazione dell'applicazione.

  3. Copia il codice nella tua app web. Ad esempio:

Versione 9 web

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);

Versione 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]'
});

Accedere con i tenant

Per accedere a un tenant, l'ID tenant deve essere trasmesso all'oggetto auth. Tieni presente che tenantId non viene mantenuto durante i ricaricamenti della pagina.

Versione 9 web

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

Versione web 8

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

Eventuali richieste di accesso future da questa istanza auth includeranno l'ID tenant (TENANT_ID1 nell'esempio precedente) finché non modifichi o reimposti l'ID tenant.

Puoi lavorare con più tenant utilizzando una o più istanze auth.

Per utilizzare una singola istanza auth, modifica la proprietà tenantId ogni volta che vuoi passare da un tenant. Per tornare agli IdP a livello di progetto, imposta tenantId su null:

Versione 9 web

// 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;

Versione 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;

Per utilizzare più istanze, crea una nuova istanza auth per ogni tenant e assegna loro ID diversi:

Versione 9 web

// 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";

Versione 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";

Dopo aver eseguito l'accesso con un tenant, un utente tenant verrà restituito con user.tenantId impostato su quel tenant. Tieni presente che se passi a tenantId sull'istanza auth in un secondo momento, la proprietà currentUser non cambierà, ma rimanderà comunque allo stesso utente del tenant precedente.

Versione 9 web

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.
  }
});

Versione 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.
  }
});

Account email/password

L'esempio seguente mostra come registrare un nuovo utente:

Versione 9 web

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

Versione web 8

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

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

Per accedere a un utente esistente:

Versione 9 web

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

Versione 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

Per accedere con un provider SAML, crea un'istanza SAMLAuthProvider con l'ID provider dalla console Google Cloud:

Versione 9 web

import { SAMLAuthProvider } from "firebase/auth";

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

Versione web 8

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

Puoi quindi utilizzare un popup o un flusso di reindirizzamento per accedere al provider SAML.

  • Popup

    Versione 9 web

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

    Versione 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.
      });

  • Reindirizzamento

    Versione 9 web

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

    Versione 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.
      });

In entrambi i casi, assicurati di impostare l'ID tenant corretto nell'istanza auth.

Per avviare il flusso di autenticazione, visualizza un'interfaccia che chiede all'utente di fornire il suo indirizzo email, quindi chiama sendSignInLinkToEmail per inviare un link di autenticazione. Assicurati di impostare l'ID tenant corretto nell'istanza auth prima di inviare l'email.

Versione 9 web

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

Versione 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
  });

Per completare l'accesso sulla pagina di destinazione, analizza prima l'ID tenant dal link email e impostalo sull'istanza auth. Quindi chiama signInWithEmailLink con l'indirizzo email dell'utente e il link email effettivo contenente il codice monouso.

Versione 9 web

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');
    });
}

Versione 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.
    });
}

Creazione di token personalizzati

La creazione di un token personalizzato sensibile al multi-tenant è identica a quello di un token personalizzato normale; se è stato impostato l'ID tenant corretto nell'istanza auth, una rivendicazione di tenant_id di primo livello verrà aggiunta al JWT risultante. Per istruzioni dettagliate su come creare e utilizzare i token personalizzati, consulta la sezione Creazione di token personalizzati.

L'esempio seguente mostra come creare un token personalizzato utilizzando l'SDK Admin:

Versione 9 web

// 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);
  });

Versione 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);
  });

Il codice seguente mostra come accedere utilizzando un token personalizzato:

Versione 9 web

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

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

Versione web 8

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

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

Tieni presente che, se gli ID tenant non corrispondono, il metodo signInWithCustomToken() non andrà a buon fine.

Collegamento di credenziali utente multi-tenant

Puoi collegare altri tipi di credenziali a un utente multi-tenant esistente. Ad esempio, se un utente ha eseguito l'autenticazione in precedenza con un provider SAML in un tenant, puoi aggiungere l'accesso tramite email/password al suo account esistente in modo che possa utilizzare uno dei due metodi per accedere al tenant.

Versione 9 web

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

Versione 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.
  });

Durante il collegamento o l'autenticazione di un utente multi-tenant esistente, auth.tenantId verrà ignorato; usa user.tenantId per specificare quale tenant utilizzare. Questo vale anche per altre API di gestione degli utenti, come updateProfile e updatePassword.

Gestione degli errori di account-esistenti-con-credenziali-diverse

Se hai abilitato l'impostazione Collega gli account che utilizzano la stessa email nella console Google Cloud, quando un utente tenta di accedere a un provider (ad esempio SAML) con un indirizzo email già esistente per un altro provider (ad esempio Google), viene generato l'errore auth/account-exists-with-different-credential (insieme a un oggetto AuthCredential).

Per completare l'accesso con il provider previsto, l'utente deve prima accedere al provider esistente (Google) e quindi collegarsi al provider AuthCredential precedente (SAML).

Per gestire questo errore puoi utilizzare un popup o un flusso di reindirizzamento.

  • Popup

    Versione 9 web

    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();
                      });
                  });
              }
            });
        }
      });

    Versione 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();
                      });
                  });
              }
            });
        }
      });
  • Reindirizzamento

    Quando utilizzi signInWithRedirect, auth/account-exists-with-different-credential verranno segnalati errori in getRedirectResult al termine del flusso di reindirizzamento.

    L'oggetto di errore contiene la proprietà error.tenantId. Poiché l'ID tenant sull'istanza auth non è permanente dopo il reindirizzamento, dovrai impostare l'ID tenant dall'oggetto di errore all'istanza auth.

    L'esempio seguente mostra come gestire l'errore:

    Versione 9 web

    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();
        });
    });

    Versione 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();
        });
    });

Disattivazione della creazione e dell'eliminazione dell'account utente finale

In alcune situazioni, vuoi che gli amministratori creino account utente invece degli account creati tramite le azioni utente. In questi casi, puoi disabilitare le azioni degli utenti tramite la nostra 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
        }
    }
}'

Sostituisci quanto segue:

  • AUTH_TOKEN: il token di autenticazione.
  • PROJECT_ID: l'ID del progetto.
  • TENANT_ID: l'ID tenant.

Passaggi successivi