Como autenticar com multilocação

Este documento mostra como autenticar usuários em um ambiente de vários locatários do Identity Platform.

Antes de começar

Verifique se você ativou a multilocação para seu projeto e configurou os locatários. Consulte Primeiros passos com a multilocação para saber como fazer isso.

Também será necessário adicionar o SDK do cliente ao aplicativo:

  1. Acesse a página do Identity Platform no console do Google Cloud.
    Acessar a página de usuários do Identity Platform

  2. No canto superior direito, clique em Detalhes da configuração da aplicação.

  3. Copie o código no seu app da Web. Por exemplo:

Versão 9 para a 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);

Versão 8 para a Web

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

Fazer login com locatários

Para fazer login em um locatário, o ID dele precisa ser transmitido para o objeto auth. Observe que tenantId não é mantido em recarregamentos de páginas.

Versão 9 para a Web

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

Versão 8 para a Web

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

As próximas solicitações de login dessa instância auth incluirão o ID de locatário (TENANT_ID1 no exemplo anterior) até que você altere ou redefina o ID de locatário.

É possível trabalhar com vários locatários usando uma ou várias instâncias auth.

Para usar uma única instância auth, modifique a propriedade tenantId sempre que quiser alternar entre locatários. Para voltar aos IdPs no nível do projeto, defina tenantId como null:

Versão 9 para a 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;

Versão 8 para a Web

// 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 várias instâncias, crie uma nova instância auth para cada locatário e atribua IDs diferentes a eles:

Versão 9 para a 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";

Versão 8 para a Web

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

Depois de fazer login com um locatário, um usuário de locatário será retornado com user.tenantId definido para esse locatário. Se você alterar tenantId na instância auth posteriormente, a propriedade currentUser não será alterada. Ela ainda apontará para o mesmo usuário que o locatário anterior.

Versão 9 para a 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.
  }
});

Versão 8 para a Web

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

Contas de e-mail/senha

O exemplo a seguir mostra como registrar um novo usuário:

Versão 9 para a 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.
    // ...
  });

Versão 8 para a Web

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

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

Para fazer login com um usuário atual:

Versão 9 para a 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.
    // ...
  });

Versão 8 para a Web

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

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

SAML

Para fazer login com um provedor SAML, instancie uma instância SAMLAuthProvider com o ID do provedor no console do Google Cloud:

Versão 9 para a Web

import { SAMLAuthProvider } from "firebase/auth";

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

Versão 8 para a Web

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

Você pode usar um pop-up ou um fluxo de redirecionamento para fazer login no provedor SAML.

  • Pop-up

    Versão 9 para a 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.
        // ...
      });

    Versão 8 para a Web

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

  • Redirecionamento

    Versão 9 para a 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.
        // ...
      });

    Versão 8 para a Web

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

Nos dois casos, defina o ID de locatário correto na instância auth.

Para iniciar o fluxo de autenticação, exiba uma interface solicitando que o usuário forneça um endereço de e-mail e, em seguida, chame sendSignInLinkToEmail para enviar um link de autenticação. Defina o ID do locatário correto na instância auth antes de enviar o e-mail.

Versão 9 para a 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.
    // ...
  });

Versão 8 para a Web

// 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 concluir o login na página de destino, primeiro analise o ID do locatário no link de e-mail e configure-o na instância auth. Em seguida, chame signInWithEmailLink com o e-mail do usuário e o link real que contém o código de uso único.

Versão 9 para a 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');
    });
}

Versão 8 para a Web

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

Como criar tokens personalizados

A criação de um token personalizado com reconhecimento de vários locatários é idêntica à criação de um token personalizado regular. Contanto que o ID de locatário correto tenha sido definido na instância auth, uma declaração tenant_id de nível superior será adicionada ao JWT resultante. Consulte Como criar tokens personalizados para instruções detalhadas sobre como criar e usar tokens personalizados.

No exemplo a seguir, mostramos como criar um token personalizado usando o SDK Admin:

Versão 9 para a 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);
  });

Versão 8 para a 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);
  });

O código a seguir demonstra como fazer login usando um token personalizado:

Versão 9 para a Web

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

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

Versão 8 para a Web

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

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

Observe que, se os IDs de locatário não corresponderem, o método signInWithCustomToken() falhará.

Como vincular credenciais de usuário de vários locatários

Você pode vincular outros tipos de credenciais a um usuário de vários locatários atual. Por exemplo, se um usuário foi autenticado anteriormente com um provedor SAML em um locatário, você pode adicionar o login por e-mail/senha à conta atual para que ele possa usar o método de login no locatário.

Versão 9 para a 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.
    // ...
  });

Versão 8 para a Web

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

Ao vincular ou autenticar novamente um usuário de vários locatários atual, auth.tenantId será ignorado. use user.tenantId para especificar qual locatário usar. Isso também se aplica a outras APIs de gerenciamento de usuários, como updateProfile e updatePassword.

Como processar os erros account-exists-with-different-credential

Se você ativou a configuração Vincular contas que usam o mesmo e-mail no console do Google Cloud, quando um usuário tentar fazer login em um provedor, como o SAML, com um e-mail que já existe para outro provedor, como o Google, o erro auth/account-exists-with-different-credential é acionado (junto com um objeto AuthCredential).

Para concluir o login com o provedor pretendido, o usuário precisará fazer login no provedor atual (Google) e, em seguida, vinculá-lo ao AuthCredential anterior (SAML).

Você pode usar um fluxo pop-up ou de redirecionamento para lidar com esse erro.

  • Pop-up

    Versão 9 para a 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();
                      });
                  });
              }
            });
        }
      });

    Versão 8 para a Web

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

    Ao usar signInWithRedirect, os erros auth/account-exists-with-different-credential serão gerados em getRedirectResult ao finalizar o fluxo de redirecionamento.

    O objeto de erro contém a propriedade error.tenantId. Como o ID de locatário na instância auth não é mantido após o redirecionamento, você precisa definir o ID de locatário do objeto de erro para a instância auth.

    O exemplo a seguir mostra como lidar com o erro:

    Versão 9 para a 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();
        });
    });

    Versão 8 para a Web

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

Como desativar a criação e a exclusão de contas de usuário final

Há situações em que você quer que os administradores criem contas de usuário em vez de as contas serem criadas por ações do usuário. Nesses casos, é possível desativar as ações do usuário pela nossa 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
        }
    }
}'

Substitua:

  • AUTH_TOKEN: o token de autenticação.
  • PROJECT_ID: o ID do projeto;
  • TENANT_ID: o ID do locatário.

A seguir