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 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:

    <script src="https://www.gstatic.com/firebasejs/x.x.x/firebase.js"></script>
    <script>
    // Initialize Identity Platform
    const config = {
      apiKey: "...",
      authDomain: "my-app-12345.firebaseapp.com"
    };
    firebase.initializeApp(config);
    </script>
    

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.

var 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 reverter aos IdPs no nível do projeto, defina tenantId como null:

// One Auth instance
// Switch to tenant1
firebase.auth().tenantId = 'tenant1';
// Switch to tenant2
firebase.auth().tenantId = 'tenant2';
// 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:

// 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 = 'tenant1';
auth2.tenantId = 'tenant2';

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.

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

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:

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 Cloud:

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.

O exemplo a seguir mostra o fluxo pop-up:

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

Redirect

O exemplo a seguir mostra o fluxo de redirecionamento:

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

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

firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings)
  .then(function() {
    // 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(function(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.

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:

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

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

firebase.auth().signInWithCustomToken(token)
  .catch((error) => {
    // Handle Errors here.
    var errorCode = error.code;
    var 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.

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

//Sign-in with popup
firebase.auth().signInWithPopup(provider)
  .then((result) => {
    // Existing user with 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 Cloud, quando um usuário tentar fazer login em um provedor, como o SAML, com um e-mail atual 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.

O exemplo a seguir mostra como lidar com erros auth/account-exists-with-different-credential ao usar signInWithPopup:

// 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.
      var pendingCred = error.credential;
      // The credential's tenantId if needed: error.tenantId
      // The provider account's email address.
      var 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(function(linkResult) {
                    // SAML account successfully linked to the existing
                    // user.
                    goToApp();
                  });
              });
          }
        });
    }
  });

Redirect

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:

// Step 1.
// User tries to sign in to SAML provider.
firebase.auth().tenantId = 'TENANT_ID';
firebase.auth().signInWithRedirect(samlProvider);
// 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.
    var tenantId = error.tenantId;
    // The pending SAML credential.
    var pendingCred = error.credential;
    // The provider account's email address.
    var 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(function(linkResult) {
      // SAML account successfully linked to the existing
      // user.
      goToApp();
    });
});

A seguir