Crea una página de acceso personalizada

En este artículo, se muestra cómo compilar tu propia página de autenticación con IAP y las identidades externas. Si creas esta página tú mismo, tendrás control total sobre el flujo de autenticación y la experiencia del usuario.

Si no necesitas personalizar por completo tu IU, puedes permitir que IAP aloje una página de acceso por ti o puedes usar FirebaseUI para obtener una experiencia más optimizada.

Descripción general

Para compilar tu propia página de autenticación, sigue estos pasos:

  1. Habilita las identidades externas. Selecciona I’ll provide my own UI option durante la configuración.
  2. Instala la biblioteca gcip-iap.
  3. Configura la IU mediante la implementación de la interfaz AuthenticationHandler. Tu página de autenticación debe controlar las siguientes situaciones:
    • Selección de usuarios
    • Autorización de usuarios
    • Acceso de usuarios
    • Manejo de errores
  4. Opcional: Personaliza la página de autenticación con funciones adicionales, como barras de progreso, páginas para salir y procesamiento de usuarios.
  5. Prueba tu IU.

Instala la biblioteca gcip-iap

Para instalar la biblioteca gcip-iap, ejecuta el siguiente comando:

npm install gcip-iap --save

El módulo de Administración de socios de red gcip-iap simplifica las comunicaciones entre tu aplicación, IAP y Identity Platform. Esto te permite personalizar todo el flujo de autenticación sin tener que administrar los intercambios subyacentes entre IAP y la IU.

Usa las importaciones correctas para la versión del SDK:

gcip-iap v0.1.4 o anterior

// Import Firebase/GCIP dependencies. These are installed on npm install.
import * as firebase from 'firebase/app';
import 'firebase/auth';
// Import GCIP/IAP module.
import * as ciap from 'gcip-iap';

gcip-iap v1.0.0 a v1.1.0

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

  • Actualiza la versión firebase en el archivo package.json a la versión 9.6.0 o posterior.
  • Actualiza las sentencias de importación firebase de la siguiente manera:
// Import Firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import the gcip-iap module.
import * as ciap from 'gcip-iap';

No se necesitan cambios adicionales en el código.

gcip-iap v2.0.0

A partir de la versión 2.0.0, gcip-iap requiere volver a escribir tu aplicación de IU personalizada con el formato de SDK modular. Si migras a gcip-iap v2.0.0 o una versión posterior, completa las siguientes acciones:

  • Actualiza la versión firebase en el archivo package.json a la versión 9.8.3 o posterior.
  • Actualiza las sentencias de importación firebase de la siguiente manera:
  // Import Firebase modules.
  import { initializeApp } from 'firebase/app';
  import { getAuth, GoogleAuthProvider } 'firebase/auth';
  // Import the gcip-iap module.
  import * as ciap from 'gcip-iap';

Configura la IU

Para configurar la IU, crea una clase personalizada que implemente la interfaz AuthenticationHandler:

interface AuthenticationHandler {
  languageCode?: string | null;
  getAuth(apiKey: string, tenantId: string | null): FirebaseAuth;
  startSignIn(auth: FirebaseAuth, match?: SelectedTenantInfo): Promise<UserCredential>;
  selectTenant?(projectConfig: ProjectConfig, tenantIds: string[]): Promise<SelectedTenantInfo>;
  completeSignOut(): Promise<void>;
  processUser?(user: User): Promise<User>;
  showProgressBar?(): void;
  hideProgressBar?(): void;
  handleError?(error: Error | CIAPError): void;
}

Durante la autenticación, la biblioteca llama automáticamente a los métodos de AuthenticationHandler.

Selecciona usuarios

Para seleccionar un usuario, implementa selectTenant(). Puedes implementar este método para elegir un usuario de manera programática o mostrar una IU para que el usuario pueda seleccionar uno.

En cualquier caso, la biblioteca utiliza el objeto SelectedTenantInfo que se muestra para completar el flujo de autenticación. Contiene el ID del usuario seleccionado, los ID de cualquier proveedor y el correo electrónico que ingresó el usuario.

Si tienes varios usuarios en tu proyecto, debes seleccionar uno antes de poder autenticar a un usuario. Si solo tienes un usuario o usas la autenticación a nivel de proyecto, no necesitas implementar selectTenant().

IAP es compatible con los mismos proveedores que Identity Platform, por ejemplo:

  • Correo electrónico y contraseña
  • OAuth (Google, Facebook, Twitter, GitHub, Microsoft, etcétera)
  • SAML
  • OIDC
  • Número de teléfono
  • Personalizados
  • Anónimo

Los tipos de autenticación personalizados, anónimos y de número de teléfono no son compatibles con los multiusuarios.

Selecciona usuarios de manera programática

Para seleccionar un usuario de manera programática, aprovecha el contexto actual. La clase Authentication contiene getOriginalURL() que muestra la URL a la que el usuario estaba accediendo antes de la autenticación.

Úsalo para encontrar una coincidencia de una lista de usuarios asociados:

// Select provider programmatically.
selectTenant(projectConfig, tenantIds) {
  return new Promise((resolve, reject) => {
    // Show UI to select the tenant.
    auth.getOriginalURL()
      .then((originalUrl) => {
        resolve({
          tenantId: getMatchingTenantBasedOnVisitedUrl(originalUrl),
          // If associated provider IDs can also be determined,
          // populate this list.
          providerIds: [],
        });
      })
      .catch(reject);
  });
}

Permite que los usuarios seleccionen usuarios

Para permitir que el usuario seleccione un usuario, muestra una lista de usuarios y haz que el usuario elija uno o pídele que ingrese su dirección de correo electrónico y, luego, localice una coincidencia según el dominio:

// Select provider by showing UI.
selectTenant(projectConfig, tenantIds) {
  return new Promise((resolve, reject) => {
    // Show UI to select the tenant.
    renderSelectTenant(
        tenantIds,
        // On tenant selection.
        (selectedTenantId) => {
          resolve({
            tenantId: selectedTenantId,
            // If associated provider IDs can also be determined,
            // populate this list.
            providerIds: [],
            // If email is available, populate this field too.
            email: undefined,
          });
        });
  });
}

Cómo autenticar usuarios

Una vez que tengas un proveedor, implementa getAuth() para mostrar una instancia de Auth que corresponda a la clave de API y al ID de usuario proporcionado. Si no se proporciona un ID de usuario, usa proveedores de identidad a nivel de proyecto.

getAuth() rastrea dónde se almacena el usuario que corresponde a la configuración proporcionada. También permite actualizar de forma silenciosa un token de ID de Identity Platform de un usuario autenticado anteriormente sin necesidad de que el usuario vuelva a ingresar sus credenciales.

Si usas varios recursos de IAP con diferentes usuarios, te recomendamos que uses una instancia de autenticación única para cada recurso. Esto permite que varios recursos con diferentes configuraciones usen la misma página de autenticación. También permite que varios usuarios accedan al mismo tiempo sin cerrar la sesión del usuario anterior.

El siguiente es un ejemplo de cómo implementar getAuth():

gcip-iap v1.0.0

getAuth(apiKey, tenantId) {
  let auth = null;
  // Make sure the expected API key is being used.
  if (apiKey !== expectedApiKey) {
    throw new Error('Invalid project!');
  }
  try {
    auth = firebase.app(tenantId || undefined).auth();
    // Tenant ID should be already set on initialization below.
  } catch (e) {
    // Use different App names for every tenant so that
    // multiple users can be signed in at the same time (one per tenant).
    const app = firebase.initializeApp(this.config, tenantId || '[DEFAULT]');
    auth = app.auth();
    // Set the tenant ID on the Auth instance.
    auth.tenantId = tenantId || null;
  }
  return auth;
}

gcip-iap v2.0.0

import {initializeApp, getApp} from 'firebase/app';
import {getAuth} from 'firebase/auth';

getAuth(apiKey, tenantId) {
  let auth = null;
  // Make sure the expected API key is being used.
  if (apiKey !== expectedApiKey) {
    throw new Error('Invalid project!');
  }
  try {
    auth = getAuth(getApp(tenantId || undefined));
    // Tenant ID should be already set on initialization below.
  } catch (e) {
    // Use different App names for every tenant so that
    // multiple users can be signed in at the same time (one per tenant).
    const app = initializeApp(this.config, tenantId || '[DEFAULT]');
    auth = getAuth(app);
    // Set the tenant ID on the Auth instance.
    auth.tenantId = tenantId || null;
  }
  return auth;
}

Permite que los usuarios accedan

Para controlar el acceso, implementa startSignIn(), muestra una IU a fin de que el usuario se autentique y, luego, muestra un UserCredential para el usuario que accedió.

En un entorno multiusuario, puedes determinar los métodos de autenticación disponibles desde SelectedTenantInfo, si se proporcionó. Esta variable contiene la misma información que muestra selectTenant().

En el siguiente ejemplo, se muestra una implementación de startSignIn() para un usuario existente con un correo electrónico y una contraseña:

gcip-iap v1.0.0

startSignIn(auth, selectedTenantInfo) {
  return new Promise((resolve, reject) => {
    // Show the UI to sign-in or sign-up a user.
    $('#sign-in-form').on('submit', (e) => {
      const email = $('#email').val();
      const password = $('#password').val();
      // Example: Ask the user for an email and password.
      // Note: The method of sign in may have already been determined from the
      // selectedTenantInfo object.
      auth.signInWithEmailAndPassword(email, password)
        .then((userCredential) => {
          resolve(userCredential);
        })
        .catch((error) => {
          // Show the error message.
        });
    });
  });
}

gcip-iap v2.0.0

import {signInWithEmailAndPassword} from 'firebase/auth';

startSignIn(auth, selectedTenantInfo) {
  return new Promise((resolve, reject) => {
    // Show the UI to sign-in or sign-up a user.
    $('#sign-in-form').on('submit', (e) => {
      const email = $('#email').val();
      const password = $('#password').val();
      // Example: Ask the user for an email and password.
      // Note: The method of sign in may have already been determined from the
      // selectedTenantInfo object.
        signInWithEmailAndPassword(auth, email, password)
        .then((userCredential) => {
          resolve(userCredential);
        })
        .catch((error) => {
          // Show the error message.
        });
    });
  });
}

También puedes acceder a los usuarios con un proveedor federado, como OIDC o SAML, mediante una ventana emergente o un redireccionamiento:

gcip-iap v1.0.0

startSignIn(auth, selectedTenantInfo) {
  // Show the UI to sign-in or sign-up a user.
  return new Promise((resolve, reject) => {
    // Provide the user multiple buttons to sign-in.
    // For example sign-in with popup using a SAML provider.
    // Note: The method of sign in may have already been determined from the
    // selectedTenantInfo object.
    const provider = new firebase.auth.SAMLAuthProvider('saml.myProvider');
    auth.signInWithPopup(provider)
      .then((userCredential) => {
        resolve(userCredential);
      })
      .catch((error) => {
        // Show the error message.
      });
    // Using redirect flow. When the page redirects back and sign-in completes,
    // ciap will detect the result and complete sign-in without any additional
    // action.
    auth.signInWithRedirect(provider);
  });
}

gcip-iap v2.0.0

import {signInWithPopup, SAMLAuthProvider} from 'firebase/auth';

startSignIn(auth, selectedTenantInfo) {
  // Show the UI to sign-in or sign-up a user.
  return new Promise((resolve, reject) => {
    // Provide the user multiple buttons to sign-in.
    // For example sign-in with popup using a SAML provider.
    // Note: The method of sign in might have already been determined from the
    // selectedTenantInfo object.
    const provider = new SAMLAuthProvider('saml.myProvider');
    signInWithPopup(auth, provider)
      .then((userCredential) => {
        resolve(userCredential);
      })
      .catch((error) => {
        // Show the error message.
      });
    // Using redirect flow. When the page redirects back and sign-in completes,
    // ciap will detect the result and complete sign-in without any additional
    // action.
    signInWithRedirect(auth, provider);
  });
}

Algunos proveedores de OAuth admiten la aprobación de una sugerencia de acceso para acceder:

gcip-iap v1.0.0

startSignIn(auth, selectedTenantInfo) {
  // Show the UI to sign-in or sign-up a user.
  return new Promise((resolve, reject) => {
    // Use selectedTenantInfo to determine the provider and pass the login hint
    // if that provider supports it and the user specified an email address.
    if (selectedTenantInfo &&
        selectedTenantInfo.providerIds &&
        selectedTenantInfo.providerIds.indexOf('microsoft.com') !== -1) {
      const provider = new firebase.auth.OAuthProvider('microsoft.com');
      provider.setCustomParameters({
        login_hint: selectedTenantInfo.email || undefined,
      });
    } else {
      // Figure out the provider used...
    }
    auth.signInWithPopup(provider)
      .then((userCredential) => {
        resolve(userCredential);
      })
      .catch((error) => {
        // Show the error message.
      });
    });
}

gcip-iap v2.0.0

import {signInWithPopup, OAuthProvider} from 'firebase/auth';

startSignIn(auth, selectedTenantInfo) {
  // Show the UI to sign in or sign up a user.
  return new Promise((resolve, reject) => {
    // Use selectedTenantInfo to determine the provider and pass the login hint
    // if that provider supports it and the user specified an email address.
    if (selectedTenantInfo &&
        selectedTenantInfo.providerIds &&
        selectedTenantInfo.providerIds.indexOf('microsoft.com') !== -1) {
      const provider = new OAuthProvider('microsoft.com');
      provider.setCustomParameters({
        login_hint: selectedTenantInfo.email || undefined,
      });
    } else {
      // Figure out the provider used...
    }
    signInWithPopup(auth, provider)
      .then((userCredential) => {
        resolve(userCredential);
      })
      .catch((error) => {
        // Show the error message.
      });
    });
}

Consulta Cómo autenticar con multiusuario para obtener más información.

Maneja los errores

Para mostrar mensajes de error a los usuarios o intentar la recuperación ante errores como los tiempos de espera de la red, implementa handleError().

En el siguiente ejemplo, se implementa handleError():

handleError(error) {
  showAlert({
    code: error.code,
    message: error.message,
    // Whether to show the retry button. This is only available if the error is
    // recoverable via retrial.
    retry: !!error.retry,
  });
  // When user clicks retry, call error.retry();
  $('.alert-link').on('click', (e) => {
    error.retry();
    e.preventDefault();
    return false;
  });
}

En la siguiente tabla, se enumeran los códigos de error específicos de IAP que se pueden mostrar. Identity Platform también puede mostrar errores. Consulta la documentación de firebase.auth.Auth.

Código de error Descripción
invalid-argument El cliente especificó un argumento no válido.
failed-precondition La solicitud no se puede ejecutar en el estado actual del sistema.
out-of-range El cliente especificó un rango no válido.
unauthenticated La solicitud no se autenticó debido a que falta un token de OAuth, no es válido o venció.
permission-denied El cliente no tiene permiso suficiente o la IU está alojada en un dominio no autorizado.
not-found No se encontró el recurso especificado.
aborted Conflicto de concurrencia, como conflicto de lectura-modificación-escritura.
already-exists El recurso que el cliente intentó crear ya existe.
resource-exhausted Sin cuota de recursos o a punto de alcanzar el límite de frecuencia.
cancelled El cliente canceló la solicitud.
data-loss Daño o pérdida de datos no recuperable.
unknown Error de servidor desconocido.
internal Error interno del servidor
not-implemented El servidor no implementó el método de la API.
unavailable Servicio no disponible
restart-process Revisa la URL que te redireccionó a esta página para reiniciar el proceso de autenticación.
deadline-exceeded Se excedió el plazo de la solicitud.
authentication-uri-fail No se pudo generar el URI de autenticación.
gcip-token-invalid Se proporcionó un token de ID de GCIP no válido.
gcip-redirect-invalid URL de redireccionamiento no válida
get-project-mapping-fail No se pudo obtener el ID del proyecto.
gcip-id-token-encryption-error Error de encriptación del token de ID de GCIP.
gcip-id-token-decryption-error Error de desencriptación del token de ID de GCIP.
gcip-id-token-unescape-error Se produjo un error sin escape de base64 seguro para la Web.
resource-missing-gcip-sign-in-url Falta la URL de autenticación GCIP para el recurso IAP especificado.

Cómo personalizar la IU

Puedes personalizar tu página de autenticación con funciones opcionales, como barras de progreso y páginas para salir.

Muestra una IU de progreso

Para mostrar una IU de progreso personalizada al usuario cada vez que el módulo gcip-iap ejecute tareas de red de larga duración, implementa showProgressBar() y hideProgressBar().

Cierre de sesión de los usuarios

En algunos casos, es posible que desees permitir que los usuarios salgan de todas las sesiones actuales que comparten la misma URL de autenticación.

Cuando un usuario sale de su cuenta, es posible que no haya una URL a la que volver a redireccionarlo. Por lo general, esto ocurre cuando un usuario sale de la cuenta de todos los usuarios asociados con una página de acceso. En este caso, implementa completeSignOut() para mostrar un mensaje que indique que el usuario salió correctamente. Si no implementas este método, aparecerá una página en blanco cuando el usuario salga de su cuenta.

Usuarios de procesamiento

Para modificar un usuario que accedió antes de redireccionar al recurso de IAP, implementa processUser().

Puedes usar este método para hacer lo siguiente:

  • Vincular a proveedores adicionales
  • Actualizar el perfil del usuario
  • Solicitar al usuario datos adicionales después del registro
  • Procesar tokens de acceso OAuth que muestra getRedirectResult() después de llamar a signInWithRedirect().

El siguiente es un ejemplo de implementación de processUser():

gcip-iap v1.0.0

processUser(user) {
  return lastAuthUsed.getRedirectResult().then(function(result) {
    // Save additional data, or ask the user for additional profile information
    // to store in database, etc.
    if (result) {
      // Save result.additionalUserInfo.
      // Save result.credential.accessToken for OAuth provider, etc.
    }
    // Return the user.
    return user;
  });
}

gcip-iap v2.0.0

import {getRedirectResult} from 'firebase/auth';

processUser(user) {
  return getRedirectResult(lastAuthUsed).then(function(result) {
    // Save additional data, or ask the user for additional profile information
    // to store in database, etc.
    if (result) {
      // Save result.additionalUserInfo.
      // Save result.credential.accessToken for OAuth provider, etc.
    }
    // Return the user.
    return user;
  });
}

Si deseas que cualquier cambio que se realice para un usuario se refleje en las reclamaciones de token de ID que IAP propaga a tu aplicación, debes forzar la actualización del token:

gcip-iap v1.0.0

processUser(user) {
  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;
  });
}

gcip-iap v2.0.0

import {updateProfile} from 'firebase/auth';

processUser(user) {
  return updateProfile(user, {
    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;
  });
}

Prueba la IU

Después de crear una clase que implemente AuthenticationHandler, puedes usarla para crear una nueva instancia de Authentication y, luego, iniciarla:

// Implement interface AuthenticationHandler.
// const authHandlerImplementation = ....
const ciapInstance = new ciap.Authentication(authHandlerImplementation);
ciapInstance.start();

Implementa tu aplicación y navega a la página de autenticación. Deberías ver tu IU de acceso personalizada.

¿Qué sigue?