Crea una página de acceso con FirebaseUI

Tu app necesita una página de acceso para usar identidades externas con Identity-Aware Proxy (IAP). IAP redireccionará a los usuarios a esta página para que se autentiquen antes de que puedan acceder a los recursos seguros.

En este artículo, se muestra cómo compilar una página de autenticación con FirebaseUI, una biblioteca de JavaScript de código abierto. FirebaseUI proporciona elementos personalizables que ayudan a reducir el código estándar y controla los flujos para que los usuarios accedan con una amplia variedad de proveedores de identidad.

Para comenzar más rápido, permite que IAP aloje la IU por ti. Esto te permite probar identidades externas sin escribir ningún código adicional. Para situaciones más avanzadas, también puedes compilar tu propia página de acceso desde cero. Esta opción es más compleja, pero te proporciona un control total del flujo de autenticación y la experiencia del usuario.

Antes de comenzar

Habilita identidades externas y selecciona la opción Proporcionaré mi propia IU durante la configuración.

Instala las bibliotecas

Instala las bibliotecas gcip-iap, firebase y firebaseui. El módulo gcip-iap abstrae las comunicaciones entre tu aplicación, IAP y Identity Platform. Las bibliotecas firebase y firebaseui proporcionan los componentes básicos de la IU de autenticación.

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

Ten en cuenta que el módulo gcip-iap no está disponible con la CDN.

Luego, puedes import los módulos en los archivos de origen. Usa las importaciones correctas para tu versión del SDK:

gcip-iap v0.1.4 o versiones anteriores

// 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 o una versión posterior

A partir de la versión 1.0.0, gcip-iap requiere la dependencia de pares firebase v9 o una posterior. Si migras a gcip-iap v1.0.0 o una versión posterior, completa las siguientes acciones:

  • Actualiza las versiones de firebase y firebaseui en tu archivo package.json a la v9.6.0 o versiones posteriores y a la v6.0.0 o versiones posteriores, respectivamente.
  • Actualiza las declaraciones de importación de firebase de la siguiente manera:
// 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.

No es necesario realizar cambios adicionales en el código.

Para obtener más opciones de instalación, incluido el uso de versiones localizadas de las bibliotecas, consulta las instrucciones en GitHub.

Configura tu aplicación

FirebaseUI usa un objeto de configuración que especifica las instancias y los proveedores que se usarán para la autenticación. Una configuración completa puede ser muy larga y podría verse de la siguiente manera:

// 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'
        }
      },
    },
  },
};

En las siguientes secciones, se brinda orientación sobre cómo configurar algunos de los campos específicos de IAP. Para ver ejemplos sobre cómo configurar otros campos, consulta el fragmento de código anterior o la documentación de FirebaseUI en GitHub.

Configura la clave de API

Una configuración típica comienza con una clave de API para tu proyecto:

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

En la mayoría de los casos, solo necesitas especificar una sola clave de API. Sin embargo, si deseas usar una sola URL de autenticación en varios proyectos, puedes incluir varias claves de API:

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

Obtén el dominio de autenticación

Configura el campo authdomain en el dominio aprovisionado para facilitar el acceso federado. Puedes recuperar este campo desde la página de Identity Platform en la consola de Google Cloud.

Especifica los ID de las instancias

Una configuración requiere una lista de instancias y proveedores con la que los usuarios pueden autenticarse.

Cada instancia se identifica según su ID. Si usas la autenticación a nivel de proyecto (sin instancias), usa el identificador _ especial como una clave de API. Por ejemplo:

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

También puedes especificar una configuración de instancia comodín con el operador *. Esta instancia se usa como resguardo si no se encuentra un ID coincidente.

Configura proveedores de instancia

Cada instancia tiene sus propios proveedores; estos se especifican en el campo signInOptions:

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

Consulta cómo configurar los proveedores de acceso en la documentación de FirebaseUI para aprender a configurar proveedores.

Además de los pasos descritos en la documentación de FirebaseUI, existen varios campos específicos de IAP que dependen del modo de selección de instancia que elijas. Consulta la siguiente sección para obtener más información sobre estos campos.

Elige un modo de selección de instancia

Los usuarios pueden seleccionar una instancia de dos maneras: options-first mode o identifier-first mode.

En el modo options, el usuario comienza seleccionando una instancia de una lista y, luego, ingresa su nombre de usuario y contraseña. En el modo identifier, el usuario primero ingresa su correo electrónico. Luego, el sistema selecciona automáticamente la primera instancia con un Proveedor de identidad que coincida con el dominio del correo electrónico.

Para usar el modo options, configura displayMode en optionFirst. Luego, deberás proporcionar información de configuración del botón de cada instancia, incluidos displayName, buttonColor y iconUrl. También se puede proporcionar una fullLabel opcional para anular la etiqueta del botón completa en lugar del nombre visible.

El siguiente es un ejemplo de una instancia configurada para usar el modo options:

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

Para usar el modo identifier, cada opción de acceso debe especificar un campo hd que indique qué dominio admite. Puede ser una regex (como /@example\.com$/) o la string del dominio (p. ej., example.com).

El siguiente código muestra una instancia configurada para usar el modo identifier:

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

Habilita el redireccionamiento inmediato

Si tu aplicación solo admite un único Proveedor de identidad, configurar immediateFederatedRedirect en true omitirá la IU de acceso y redireccionará al usuario directamente al proveedor.

Configura devoluciones de llamadas

El objeto de configuración contiene un conjunto de devoluciones de llamadas que se invocan en varios puntos durante el flujo de autenticación. Además, esto te permite personalizar la IU de forma más completa. Están disponibles los siguientes hooks:

selectTenantUiShown() Se activa cuando se muestra la IU para seleccionar una instancia. Úsalo si quieres modificar la IU con un título o un tema personalizados.
signInUiShown(tenantId) Se activa cuando se selecciona una instancia y se muestra la IU para que el usuario ingrese sus credenciales. Úsalo si quieres modificar la IU con un título o un tema personalizados.
beforeSignInSuccess(user) Se activa antes de que se complete el acceso. Úsalo para modificar un usuario con sesión iniciada antes de que se lo redireccione al recurso de IAP.

El siguiente código de ejemplo muestra cómo puedes implementar estas devoluciones de llamadas:

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

Inicializa la biblioteca

Una vez que hayas creado un objeto de configuración, sigue estos pasos para inicializar la biblioteca en tu página de autenticación:

  1. Crea un contenedor HTML para procesar la IU.

    <!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. Crea una instancia FirebaseUiHandler para procesar en el contenedor HTML y pásale el elemento config que creaste.

    const configs = {
      // ...
    }
    const handler = new firebaseui.auth.FirebaseUiHandler(
      '#firebaseui-auth-container', configs);
    
  3. Crea una nueva instancia Authentication, pásale el controlador y llama a start().

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

Implementa tu aplicación y navega a la página de autenticación. Debería aparecer una IU de acceso que incluya las instancias y los proveedores.

¿Qué sigue?