Como criar uma página de login com a FirebaseUI

Para usar identidades externas com o Identity-Aware Proxy (IAP), seu app precisa de uma página de login. O IAP redirecionará os usuários para esta página para autenticação antes que eles acessem recursos seguros.

Neste artigo, você aprenderá como criar uma página de autenticação usando o FirebaseUI (em inglês), uma biblioteca JavaScript de código aberto. A FirebaseUI fornece elementos personalizáveis que ajudam a reduzir o código de texto clichê e processa os fluxos de login de usuários com uma ampla variedade de provedores de identidade.

Para começar mais rapidamente, deixe o IAP hospedar a IU para você. Isso permite que você teste identidades externas sem escrever códigos adicionais. Para cenários mais avançados, também é possível criar sua própria página de login do zero. Essa opção é mais complexa, mas oferece controle total sobre o fluxo de autenticação e a experiência do usuário.

Antes de começar

Ativar identidades externas e selecionar a opção Fornecerei minha própria IU durante a configuração.

Como instalar as bibliotecas

É necessário instalar as bibliotecas gcip-iap, firebase e firebaseui. O módulo gcip-iap abstrai as comunicações entre seu app, o IAP e o Identity Platform. As bibliotecas firebase e firebaseui fornecem os elementos fundamentais para a criação da IU de autenticação.

npm install firebase --save
npm install firebaseui --save
npm install gcip-iap --save

O módulo gcip-iap não está disponível por meio da CDN.

Em seguida, faça uma operação import para importar os módulos para seus arquivos de origem. Use as importações corretas para sua versão do SDK:

gcip-iap v0.1.4 ou anterior

// Import firebase modules.
import * as firebase from "firebase/app";
import "firebase/auth";
// Import firebaseui module.
import * as firebaseui from 'firebaseui'
// Import gcip-iap module.
import * as ciap from 'gcip-iap';

gcip-iap v1.0.0 ou mais recente

A partir da versão v1.0.0, o gcip-iap requer a dependência de peer firebase v9 ou mais recente. Se você estiver migrando para a gcip-iap v1.0.0 ou mais recente, conclua as seguintes ações:

  • Atualize as versões firebase e firebaseui no arquivo package.json para v9.6.0+ e v6.0.0+, respectivamente.
  • Atualize as instruções de importação firebase da seguinte maneira:
// Import firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import firebaseui module.
import * as firebaseui from 'firebaseui'
// Import gcip-iap module.

Não é necessário fazer outras mudanças no código.

Para outras opções de instalação, incluindo com o uso de versões localizadas das bibliotecas, consulte as instruções no GitHub (em inglês).

Como configurar seu aplicativo

O FirebaseUI usa um objeto de configuração que especifica os locatários e provedores a serem usados na autenticação. A configuração completa pode ser muito longa e ter esta aparência:

// The project configuration.
const configs = {
  // Configuration for project identified by API key API_KEY1.
  API_KEY1: {
    authDomain: 'project-id1.firebaseapp.com',
    // Decide whether to ask user for identifier to figure out
    // what tenant to select or whether to present all the tenants to select from.
    displayMode: 'optionFirst', // Or identifierFirst
    // The terms of service URL and privacy policy URL for the page
    // where the user select tenant or enter email for tenant/provider
    // matching.
    tosUrl: 'http://localhost/tos',
    privacyPolicyUrl: 'http://localhost/privacypolicy',
    callbacks: {
      // The callback to trigger when the selection tenant page
      // or enter email for tenant matching page is shown.
      selectTenantUiShown: () => {
        // Show title and additional display info.
      },
      // The callback to trigger when the sign-in page
      // is shown.
      signInUiShown: (tenantId) => {
        // Show tenant title and additional display info.
      },
      beforeSignInSuccess: (user) => {
        // Do additional processing on user before sign-in is
        // complete.
        return Promise.resolve(user);
      }
    },
    tenants: {
      // Tenant configuration for tenant ID tenantId1.
      tenantId1: {
        // Full label, display name, button color and icon URL of the
        // tenant selection button. Only needed if you are
        // using the option first option.
        fullLabel: 'ACME Portal',
        displayName: 'ACME',
        buttonColor: '#2F2F2F',
        iconUrl: '<icon-url-of-sign-in-button>',
         // Sign-in providers enabled for tenantId1.
        signInOptions: [
          // Microsoft sign-in.
          {
            provider: 'microsoft.com',
            providerName: 'Microsoft',
            buttonColor: '#2F2F2F',
            iconUrl: '<icon-url-of-sign-in-button>',
            loginHintKey: 'login_hint'
          },
          // Email/password sign-in.
          {
            provider: 'password',
            // Do not require display name on sign up.
            requireDisplayName: false,
            disableSignUp: {
              // Disable user from signing up with email providers.
              status: true,
              adminEmail: 'admin@example.com',
              helpLink: 'https://www.example.com/trouble_signing_in'
            }
          },
          // SAML provider. (multiple SAML providers can be passed)
          {
            provider: 'saml.my-provider1',
            providerName: 'SAML provider',
            fullLabel: 'Employee Login',
            buttonColor: '#4666FF',
            iconUrl: 'https://www.example.com/photos/my_idp/saml.png'
          },
        ],
        // If there is only one sign-in provider eligible for the user,
        // whether to show the provider selection page.
        immediateFederatedRedirect: true,
        signInFlow: 'redirect', // Or popup
        // The terms of service URL and privacy policy URL for the sign-in page
        // specific to each tenant.
        tosUrl: 'http://localhost/tenant1/tos',
        privacyPolicyUrl: 'http://localhost/tenant1/privacypolicy'
      },
      // Tenant configuration for tenant ID tenantId2.
      tenantId2: {
        fullLabel: 'OCP Portal',
        displayName: 'OCP',
        buttonColor: '#2F2F2F',
        iconUrl: '<icon-url-of-sign-in-button>',
        // Tenant2 supports a SAML, OIDC and Email/password sign-in.
        signInOptions: [
          // Email/password sign-in.
          {
            provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
            // Do not require display name on sign up.
            requireDisplayName: false
          },
          // SAML provider. (multiple SAML providers can be passed)
          {
            provider: 'saml.my-provider2',
            providerName: 'SAML provider',
            fullLabel: 'Contractor Portal',
            buttonColor: '#4666FF',
            iconUrl: 'https://www.example.com/photos/my_idp/saml.png'
          },
          // OIDC provider. (multiple OIDC providers can be passed)
          {
            provider: 'oidc.my-provider1',
            providerName: 'OIDC provider',
            buttonColor: '#4666FF',
            iconUrl: 'https://www.example.com/photos/my_idp/oidc.png'
          },
        ],
      },
      // Tenant configuration for tenant ID tenantId3.
      tenantId3: {
        fullLabel: 'Tenant3 Portal',
        displayName: 'Tenant3',
        buttonColor: '#007bff',
        iconUrl: '<icon-url-of-sign-in-button>',
        // Tenant3 supports a Google and Email/password sign-in.
        signInOptions: [
          // Email/password sign-in.
          {
            provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
            // Do not require display name on sign up.
            requireDisplayName: false
          },
          // Google provider.
          {
            provider: 'google.com',
            scopes: ['scope1', 'scope2', 'https://example.com/scope3'],
            loginHintKey: 'login_hint',
            customParameters: {
              prompt: 'consent',
            },
          },
        ],
        // Sets the adminRestrictedOperation configuration for providers
        // including federated, email/password, email link and phone number.
        adminRestrictedOperation: {
          status: true,
          adminEmail: 'admin@example.com',
          helpLink: 'https://www.example.com/trouble_signing_in'
        }
      },
    },
  },
};

Veja nas seções a seguir as orientações sobre como configurar alguns dos campos específicos do IAP. Para exemplos sobre como definir outros campos, consulte o snippet de código acima ou a documentação do FirebaseUI no GitHub (em inglês).

Como definir a chave de API

Uma configuração típica começa com a chave de API do projeto:

// The project configuration.
const configs = {
  // Configuration for API_KEY.
  API_KEY: {
    // Config goes here
  }
}

Na maioria dos casos, você só precisa especificar uma única chave de API. No entanto, se você quiser usar o mesmo URL de autenticação em vários projetos, inclua várias chaves de API:

const configs = {
  API_KEY1: {
    // Config goes here
  },
  API_KEY2: {
    // Config goes here
  },
}

Como saber o domínio de autenticação

Defina o campo authdomain como o domínio provisionado para facilitar o login federado. Para descobrir o valor desse campo, acesse a página do Identity Platform no console do Google Cloud.

Como especificar IDs de locatários

Para fazer a configuração, é necessário informar uma lista de locatários e provedores que serão utilizados na autenticação dos usuários.

Cada locatário é identificado por um ID. Se você adotou a autenticação para envolvidos no projeto (sem locatários), use o identificador especial "_" como chave de API. Por exemplo:

const configs = {
  // Configuration for project identified by API key API_KEY1.
  API_KEY1: {
    tenants: {
      // Project-level IdPs flow.
      _: {
        // Tenant config goes here
      },
      // Single tenant flow.
      1036546636501: {
        // Tenant config goes here
      }
    }
  }
}

Também é possível especificar uma configuração de locatário com caractere curinga usando o operador "*". Esse locatário servirá como substituto se nenhum ID correspondente for encontrado.

Como configurar provedores de locatários

Cada locatário tem os próprios provedores, que são especificados no campo signInOptions:

tenantId1: {
  signInOptions: [
    // Options go here
  ]
}

Consulte Como configurar provedores de login (em inglês) na documentação do FirebaseUI para mais informações.

Além das etapas descritas na documentação do FirebaseUI, existem vários campos específicos para o IAP que dependem do modo de seleção de locatário escolhido. Para mais informações sobre esses campos, consulte a próxima seção.

Como escolher um modo de seleção de locatário

Os usuários podem selecionar um locatário de duas maneiras: modo primeiro as opções ou modo primeiro o identificador.

No modo das opções, o usuário começa selecionando um locatário em uma lista e depois digita o nome de usuário e a senha. No modo do identificador, o usuário primeiro insere o e-mail. Então, o sistema seleciona automaticamente o primeiro locatário com um provedor de identidade correspondente ao domínio do e-mail.

Para usar o modo das opções, defina displayMode como optionFirst. Em seguida, você precisará fornecer as informações de configuração para o botão de cada locatário, incluindo displayName, buttonColor e iconUrl. Um fullLabel opcional também pode ser fornecido para substituir todo o rótulo do botão, em vez de apenas o nome de exibição.

Veja a seguir um exemplo de locatário configurado para usar o modo das opções:

tenantId1: {
  fullLabel: 'ACME Portal',
  displayName: 'ACME',
  buttonColor: '#2F2F2F',
  iconUrl: '<icon-url-of-sign-in-button>',
  // ...

Para usar o modo do identificador, é necessário que em cada opção de login haja um campo hd especificado, indicando o domínio aceito. Esse valor pode ser uma regex, como /@example\.com$/, ou a string do domínio, como example.com.

O código abaixo mostra um locatário configurado para usar o modo do identificador:

tenantId1: {
  signInOptions: [
    // Email/password sign-in.
    {
      hd: 'acme.com', // using regex: /@acme\.com$/
      // ...
    },

Como ativar o redirecionamento imediato

Se seu app aceitar um único provedor de identidade, definir immediateFederatedRedirect como true fará com que a IU de login seja ignorada e redirecionará o usuário diretamente para o provedor.

Como configurar callbacks

O objeto de configuração contém um conjunto de callbacks invocados em vários pontos durante o fluxo de autenticação. Com isso, é possível personalizar ainda mais a IU. Os hooks a seguir estão disponíveis:

selectTenantUiShown() Acionado quando a IU para selecionar um locatário é exibida. Use-o se você quiser modificar a IU com um título ou tema personalizado.
signInUiShown(tenantId) Acionado quando um locatário é selecionado e é exibida a IU para que o usuário insira as credenciais. Use-o se você quiser modificar a IU com um título ou tema personalizado.
beforeSignInSuccess(user) Acionado antes do término do login. Use-o para modificar um usuário conectado antes de redirecionar para o recurso do IAP.

O exemplo de código a seguir mostra como implementar esses callbacks:

callbacks: {
  selectTenantUiShown: () => {
    // Show info of the IAP resource.
    showUiTitle(
        'Select your employer to access your Health Benefits');
  },
  signInUiShown: (tenantId) => {
    // Show tenant title and additional display info.
    const tenantName = getTenantNameFromId(tenantId);
    showUiTitle(`Sign in to access your ${tenantName} Health Benefits`);
  },
  beforeSignInSuccess: (user) => {
    // Do additional processing on user before sign-in is
    // complete.
    // For example update the user profile.
    return user.updateProfile({
      photoURL: 'https://example.com/profile/1234/photo.png',
    }).then(function() {
      // To reflect updated photoURL in the ID token, force token
      // refresh.
      return user.getIdToken(true);
    }).then(function() {
      return user;
    });
  }
}

Como inicializar a biblioteca

Depois de criar um objeto de configuração, siga estas etapas para inicializar a biblioteca na sua página de autenticação:

  1. Crie o contêiner HTML em que a IU será renderizada.

    <!DOCTYPE html>
    <html>
     <head>...</head>
     <body>
       <!-- The surrounding HTML is left untouched by FirebaseUI.
            Your app may use that space for branding, controls and other
            customizations.-->
       <h1>Welcome to My Awesome App</h1>
       <div id="firebaseui-auth-container"></div>
     </body>
    </html>
    
  2. Crie uma instância FirebaseUiHandler a ser renderizada no contêiner HTML e transmita para ela o elemento config que você criou.

    const configs = {
      // ...
    }
    const handler = new firebaseui.auth.FirebaseUiHandler(
      '#firebaseui-auth-container', configs);
    
  3. Crie uma nova instância Authentication, transmita o gerenciador para ela e chame start().

    const ciapInstance = new ciap.Authentication(handler);
    ciapInstance.start();
    

Implante seu aplicativo e navegue até a página de autenticação. Você verá uma IU de login contendo seus locatários e provedores.

A seguir