Autenticação com multi-tenancy
Este documento mostra como autenticar utilizadores num ambiente do Identity Platform com vários inquilinos.
Antes de começar
Certifique-se de que ativou a multilocação para o seu projeto e configurou os seus inquilinos. Consulte o artigo Introdução à multilocação para saber como.
Também tem de adicionar o SDK do cliente à sua app:
Aceda à página do Identity Platform na Google Cloud consola.
Aceda à página de utilizadores da Identity PlatformNa parte superior direita, clique em Detalhes da configuração da aplicação.
Copie o código para a sua app Web. Por exemplo:
Versão 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);
Versão 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]' });
Inicie sessão com inquilinos
Para iniciar sessão num inquilino, o ID do inquilino tem de ser transmitido ao objeto auth
.
Tenha em atenção que tenantId
não é mantido nas atualizações de páginas.
Versão Web 9
import { getAuth } from "firebase/auth"; const auth = getAuth(); const tenantId = "TENANT_ID1"; auth.tenantId = tenantId;
Versão Web 8
const tenantId = "TENANT_ID1"; firebase.auth().tenantId = tenantId;
Todos os pedidos de início de sessão futuros desta instância auth
vão incluir o ID do inquilino (TENANT_ID1
no exemplo anterior) até alterar ou repor o ID do inquilino.
Pode trabalhar com vários inquilinos usando instâncias únicas ou múltiplas do auth
.
Para usar uma única instância auth
, modifique a propriedade tenantId
sempre que quiser alternar entre inquilinos. Para reverter para IdPs ao nível do projeto, defina tenantId
como null
:
Versão 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;
Versão 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 várias instâncias, crie uma nova instância auth
para cada inquilino e atribua-lhes IDs diferentes:
Versão 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";
Versão 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";
Depois de iniciar sessão com um inquilino, é devolvido um utilizador do inquilino com
user.tenantId
definido para esse inquilino. Tenha em atenção que, se ativar a opção tenantId
na instância auth
mais tarde, a propriedade currentUser
não se altera. Continua a apontar para o mesmo utilizador que o inquilino anterior.
Versão 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. } });
Versão 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. } });
Contas de email/palavra-passe
O exemplo seguinte mostra como registar um novo utilizador:
Versão 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. // ... });
Versão 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 sessão como um utilizador existente:
Versão 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. // ... });
Versão 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 sessão com um fornecedor SAML, instancie uma instância SAMLAuthProvider
com o ID do fornecedor da consola Google Cloud :
Versão Web 9
import { SAMLAuthProvider } from "firebase/auth"; const provider = new SAMLAuthProvider("saml.myProvider");
Versão Web 8
const provider = new firebase.auth.SAMLAuthProvider('saml.myProvider');
Em seguida, pode usar um fluxo de pop-up ou de redirecionamento para iniciar sessão no fornecedor de SAML.
Pop-up
Versão 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. // ... });
Versão 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. });
Redirecionar
Versão 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. // ... });
Versão 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. });
Em ambos os casos, certifique-se de que define o ID do inquilino correto na instância auth
.
Link de email
Para iniciar o fluxo de autenticação, apresente uma interface que peça ao utilizador para fornecer o respetivo endereço de email e, em seguida, chame sendSignInLinkToEmail
para lhe enviar um link de autenticação. Certifique-se de que define o ID do inquilino correto na instância auth
antes de enviar o email.
Versão 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. // ... });
Versão 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 concluir o início de sessão na página de destino, analise primeiro o ID do inquilino a partir do link do email e defina-o na instância auth
. Em seguida, chame signInWithEmailLink
com o email do utilizador e o link de email real que contém o código único.
Versão 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'); }); }
Versão 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. }); }
Criar tokens personalizados
A criação de um token personalizado com reconhecimento de vários inquilinos é idêntica à criação de um token personalizado normal. Desde que o ID do inquilino correto tenha sido definido na instância auth
, é adicionada uma reivindicação tenant_id
de nível superior ao JWT resultante.
Consulte o artigo Criar tokens personalizados
para obter instruções detalhadas sobre como criar e usar tokens personalizados.
O exemplo seguinte mostra como criar um token personalizado através do SDK de administrador:
Versão 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); });
Versão 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); });
O código seguinte demonstra como iniciar sessão através de um token personalizado:
Versão Web 9
import { signInWithCustomToken } from "firebase/auth"; auth.tenantId = 'TENANT_ID1'; signInWithCustomToken(auth, token) .catch((error) => { // Handle / display error. // ... });
Versão Web 8
firebase.auth().tenantId = 'TENANT_ID1'; firebase.auth().signInWithCustomToken(token) .catch((error) => { // Handle Errors here. const errorCode = error.code; const errorMessage = error.message; // ... });
Tenha em atenção que, se os IDs dos inquilinos não corresponderem, o método signInWithCustomToken()
falha.
Associar credenciais de utilizador multiinquilino
Pode associar outros tipos de credenciais a um utilizador multiinquilino existente. Por exemplo, se um utilizador tiver sido autenticado anteriormente com um fornecedor de SAML num inquilino, pode adicionar o início de sessão por email/palavra-passe à respetiva conta existente para que possa utilizar qualquer um dos métodos para iniciar sessão no inquilino.
Versão 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. // ... });
Versão 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. });
Quando associar ou reautenticar um utilizador multi-inquilino existente, o elemento auth.tenantId
é ignorado. Use user.tenantId
para especificar que inquilino usar. Isto também se aplica a outras APIs de gestão de utilizadores, como a updateProfile
e a updatePassword
.
Como processar os erros account-exists-with-different-credential
Se ativou a definição Associar contas que usam o mesmo email na
Google Cloud consola, quando um utilizador tenta iniciar sessão num fornecedor
(como SAML) com um email que já existe para outro fornecedor (como
Google), é apresentado o erro auth/account-exists-with-different-credential
(juntamente com um objeto AuthCredential
).
Para concluir o início de sessão com o fornecedor pretendido, o utilizador tem de iniciar sessão primeiro no fornecedor existente (Google) e, em seguida, associar ao anterior AuthCredential
(SAML).
Pode usar um fluxo de pop-up ou de redirecionamento para resolver este erro.
Pop-up
Versão 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(); }); }); } }); } });
Versão 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(); }); }); } }); } });
Redirecionar
Quando usar
signInWithRedirect
,auth/account-exists-with-different-credential
, são gerados erros emgetRedirectResult
quando terminar o fluxo de redirecionamento.O objeto de erro contém a propriedade
error.tenantId
. Uma vez que o ID do inquilino na instânciaauth
não é mantido após o redirecionamento, tem de definir o ID do inquilino do objeto de erro para a instânciaauth
.O exemplo seguinte mostra como processar o erro:
Versão 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(); }); });
Versão 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(); }); });
Desativar a criação e a eliminação de contas de utilizadores finais
Existem situações em que quer que os administradores criem contas de utilizador em vez de as contas serem criadas através de ações do utilizador. Nestes casos, pode desativar as ações do utilizador através da 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 o seguinte:
AUTH_TOKEN
: o token de autorização.PROJECT_ID
: o ID do projeto.TENANT_ID
: o ID do inquilino.
O que se segue?
- Crie uma página de início de sessão para vários inquilinos
- Migre utilizadores existentes para um inquilino
- Faça a gestão dos inquilinos através de programação