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, você pode import os módulos nos 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 peering 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 desta forma:
// 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.

Nenhuma outra alteração de código é necessária.

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. É possível recuperar esse campo na 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