S'authentifier avec une architecture mutualisée

Ce document explique comment authentifier les utilisateurs dans un environnement Identity Platform mutualisé.

Avant de commencer

Assurez-vous d'avoir activé l'architecture mutualisée pour votre projet et configuré vos locataires. Pour en savoir plus, consultez la page Premiers pas avec l'architecture mutualisée.

Vous devez également ajouter le SDK client à votre application :

  1. Accédez à la page "Identity Platform" dans la console Google Cloud.
    Accéder à la page des utilisateurs "Identity Platform"

  2. En haut à droite, cliquez sur Informations sur la configuration de l'application.

  3. Copiez le code dans votre application Web. Exemples :

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

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

Se connecter avec des locataires

Pour vous connecter à un locataire, l'ID de locataire doit être transmis à l'objet auth. Notez que tenantId n'est pas conservé lors de l'actualisation de la page.

Version Web 9

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

Version Web 8

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

Toute demande de connexion ultérieure depuis cette instance auth inclura l'ID de locataire (TENANT_ID1 dans l'exemple précédent) jusqu'à ce que vous modifiiez ou réinitialisiez l'ID de locataire.

Vous pouvez utiliser plusieurs locataires à l'aide d'une ou de plusieurs instances auth.

Pour utiliser une seule instance de auth, modifiez la propriété tenantId chaque fois que vous souhaitez basculer entre les locataires. Pour revenir aux fournisseurs d'identité au niveau du projet, définissez tenantId sur null :

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

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

Pour utiliser plusieurs instances, créez une instance auth pour chaque locataire et attribuez aux instances des ID différents :

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

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

Après la connexion avec un locataire, un utilisateur locataire est renvoyé avec l'option user.tenantId définie sur ce locataire. Notez que si vous basculez tenantId sur l'instance auth ultérieurement, la propriété currentUser ne sera pas modifiée. Elle pointera toujours vers le même utilisateur que le locataire précédent.

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

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

Comptes de messagerie/mot de passe

L'exemple suivant montre comment enregistrer un nouvel utilisateur :

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

Version Web 8

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

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

Pour vous connecter à un utilisateur existant :

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

Version 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

Pour vous connecter avec un fournisseur SAML, instanciez une instance SAMLAuthProvider avec l'ID du fournisseur à partir de la console Google Cloud:

Version Web 9

import { SAMLAuthProvider } from "firebase/auth";

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

Version Web 8

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

Vous pouvez ensuite utiliser une fenêtre pop-up ou un flux de redirection pour vous connecter au fournisseur SAML.

  • Fenêtre pop-up

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

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

  • Redirection

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

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

Dans les deux cas, veillez à définir l'ID de locataire approprié sur l'instance auth.

Pour lancer le flux d'authentification, affichez une interface invitant l'utilisateur à indiquer son adresse e-mail, puis appelez sendSignInLinkToEmail pour lui envoyer un lien d'authentification. Veillez à définir l'ID de locataire approprié sur l'instance auth avant d'envoyer l'e-mail.

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

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

Pour terminer la connexion sur la page de destination, analysez d'abord l'ID de locataire à partir du lien d'e-mail et définissez-le sur l'instance auth. Ensuite, appelez signInWithEmailLink avec l'adresse e-mail de l'utilisateur et le lien d'e-mail réel contenant le code à usage unique.

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

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

Créer des jetons personnalisés

La création d'un jeton personnalisé mutualisé est identique à la création d'un jeton personnalisé standard. Tant que l'ID de locataire approprié a été défini sur l'instance auth, une revendication tenant_id de premier niveau est ajoutée au JWT obtenu. Consultez la section Créer des jetons personnalisés pour obtenir des instructions détaillées sur la création et l'utilisation de jetons personnalisés.

L'exemple suivant montre comment créer un jeton personnalisé à l'aide du SDK Admin :

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

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

Le code suivant montre comment se connecter à l'aide d'un jeton personnalisé :

Version Web 9

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

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

Version Web 8

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

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

Notez que si les ID de locataire ne correspondent pas, la méthode signInWithCustomToken() échoue.

Associer des identifiants utilisateur mutualisés

Vous pouvez associer d'autres types d'identifiants à un utilisateur mutualisé existant. Par exemple, si un utilisateur s'est précédemment authentifié auprès d'un fournisseur SAML dans un locataire, vous pouvez ajouter une connexion par e-mail/mot de passe à son compte existant pour qu'il puisse utiliser l'une ou l'autre méthode de connexion au locataire.

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

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

Lorsque vous associez ou réauthentifiez un utilisateur mutualisé existant, auth.tenantId est ignoré. Utilisez user.tenantId pour spécifier le locataire à utiliser. Cela s'applique également aux autres API de gestion des utilisateurs, telles que updateProfile et updatePassword.

Gérer les erreurs de compte existant avec un identifiant différent

Si vous avez activé le paramètre Associer des comptes qui utilisent la même adresse e-mail dans la console Google Cloud, lorsqu'un utilisateur tente de se connecter à un fournisseur (tel que SAML) avec une adresse e-mail qui existe déjà pour un autre fournisseur (tel que Google), l'erreur auth/account-exists-with-different-credential est générée (avec un objet AuthCredential).

Pour terminer la connexion au fournisseur désiré, l'utilisateur doit d'abord se connecter au fournisseur existant (Google), puis l'associer à l'ancien AuthCredential (SAML).

Vous pouvez utiliser une fenêtre pop-up ou un flux de redirection pour gérer cette erreur.

  • Fenêtre pop-up

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

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

    Lorsque vous utilisez signInWithRedirect, des erreurs auth/account-exists-with-different-credential sont générées dans getRedirectResult à la fin du flux de redirection.

    L'objet d'erreur contient la propriété error.tenantId. Comme l'ID de locataire sur l'instance auth n'est pas conservé après la redirection, vous devez définir l'ID de locataire de l'objet d'erreur sur l'instance auth.

    L'exemple suivant montre comment gérer l'erreur :

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

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

Désactiver la création et la suppression de comptes d'utilisateurs finaux

Dans certains cas, vous pouvez souhaiter que les administrateurs créent des comptes d'utilisateurs plutôt que des comptes créés par des actions des utilisateurs. Dans ce cas, vous pouvez désactiver les actions utilisateur via notre 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
        }
    }
}'

Remplacez les éléments suivants :

  • AUTH_TOKEN: jeton d'authentification.
  • PROJECT_ID : ID du projet.
  • TENANT_ID: ID du locataire.

Étapes suivantes