Créer une page de connexion avec FirebaseUI

Pour utiliser des identités externes avec Identity Aware Proxy (IAP), votre application doit disposer d'une page de connexion. IAP redirige les utilisateurs vers cette page afin qu'ils puissent s'authentifier et ainsi accéder à des ressources sécurisées.

Cet article explique comment créer une page d'authentification à l'aide de FirebaseUI, une bibliothèque JavaScript Open Source. FirebaseUI fournit des éléments personnalisables qui permettent de réduire le code récurrent et gère les flux de connexion des utilisateurs avec un large éventail de fournisseurs d'identité.

Pour gagner du temps, laissez IAP héberger l'interface utilisateur à votre place. Cela vous permet d'essayer les identités externes sans avoir à écrire de code supplémentaire. Pour les scénarios plus avancés, vous pouvez choisir de créer votre propre page de connexion à partir de zéro. Cette solution est plus complexe, mais vous donne un contrôle total sur le flux d'authentification et l'expérience utilisateur.

Avant de commencer

Activez les identités externes, puis sélectionner l'option Je fournirai ma propre interface utilisateur lors de la configuration.

Installer les bibliothèques

Installez les bibliothèques gcip-iap, firebase et firebaseui. Le module gcip-iap fait abstraction des communications entre votre application, IAP et Identity Platform. Les bibliothèques firebase et firebaseui fournissent les principaux composants de votre UI d'authentification.

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

Notez que le module gcip-iap n'est pas disponible avec CDN.

Vous pouvez ensuite import les modules dans vos fichiers source. Utilisez les importations appropriées pour votre version de SDK:

gcip-iap v0.1.4 ou version antérieure

// 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 version ultérieure

À partir de la version 1.0.0, gcip-iap nécessite la dépendance de paire firebase v9 ou ultérieure. Si vous migrez vers la version 1.0.0 ou ultérieure de gcip-iap, procédez comme suit:

  • Mettez à jour les versions firebase et firebaseui de votre fichier package.json vers les versions 9.6.0 et 6.0.0, respectivement.
  • Mettez à jour les instructions d'importation firebase comme suit:
// 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.

Aucune modification de code supplémentaire n'est nécessaire.

Pour obtenir des options d'installation supplémentaires, par exemple en utilisant les versions localisées des bibliothèques, consultez les instructions sur GitHub.

Configurer votre application

FirebaseUI utilise un objet de configuration qui spécifie les locataires et les fournisseurs à utiliser pour l'authentification. L'exécution d'une configuration complète peut être très longue, et elle peut ressembler à ceci :

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

Les sections suivantes décrivent comment configurer certains champs propres à IAP. Pour obtenir des exemples de définition d'autres champs, consultez l'extrait de code ci-dessus ou la documentation FirebaseUI sur GitHub.

Définir la clé API

Une configuration standard commence par la définition d'une clé API pour votre projet :

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

Dans la plupart des cas, vous n'avez besoin de spécifier qu'une seule clé API. Toutefois, si vous souhaitez utiliser une même URL d'authentification pour plusieurs projets, vous pouvez inclure plusieurs clés API :

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

Obtenir le domaine d'authentification

Définissez le champ authdomain sur le domaine provisionné pour faciliter la connexion fédérée. Vous pouvez récupérer ce champ à partir de la page Identity Platform de la console Google Cloud.

Spécifier des ID de locataires

Une configuration nécessite une liste de locataires et de fournisseurs avec lesquels les utilisateurs peuvent s'authentifier.

Chaque locataire est identifié par son ID. Si vous utilisez l'authentification au niveau du projet (aucun locataire), utilisez plutôt l'identifiant spécial _ comme clé API. Exemple :

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

Vous pouvez également spécifier une configuration de locataire avec caractères génériques à l'aide de l'opérateur *. Ce locataire est utilisé en remplacement si aucun ID correspondant n'est trouvé.

Configurer les fournisseurs des locataires

Chaque locataire dispose de ses propres fournisseurs. Ceux-ci sont spécifiés dans le champ signInOptions :

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

Pour savoir comment configurer les fournisseurs, consultez la section Configuring sign-in providers dans la documentation FirebaseUI.

En plus des étapes décrites dans la documentation FirebaseUI, plusieurs champs propres à IAP dépendent du mode de sélection du locataire que vous choisissez. Pour plus d'informations sur ces champs, consultez la section ci-après.

Choisir un mode de sélection du locataire

Les utilisateurs peuvent sélectionner un locataire de deux manières : avec le mode Options d'abord ou avec le mode Identifiant d'abord.

En mode "Options", l'utilisateur commence par sélectionner un locataire dans une liste, puis il saisit son nom d'utilisateur et son mot de passe. En mode "Identifiant", l'utilisateur saisit d'abord son adresse e-mail. Le système sélectionne ensuite automatiquement le premier locataire ayant un fournisseur d'identité correspondant au domaine de l'adresse e-mail.

Pour utiliser le mode "Options", définissez displayMode sur optionFirst. Vous devez ensuite fournir des informations de configuration pour le bouton de chaque locataire, par exemple displayName, buttonColor et iconUrl. Vous pouvez également spécifier le champ facultatif fullLabel pour remplacer l'intégralité du libellé du bouton et non juste le nom à afficher.

Voici un exemple de locataire configuré pour utiliser le mode "Options" :

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

Si vous souhaitez utiliser le mode "Identifiant", chaque option de connexion doit spécifier un champ hd indiquant le domaine pris en charge. Il peut s'agir d'une expression régulière (telle que /@example\.com$/) ou de la chaîne de domaine (par exemple, example.com).

Le code ci-dessous présente un locataire configuré pour utiliser le mode "Identifiant" :

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

Activer la redirection immédiate

Si votre application n'est compatible qu'avec un seul fournisseur d'identité, la définition de immediateFederatedRedirect sur true permet d'ignorer l'UI de connexion et de rediriger directement l'utilisateur vers le fournisseur.

Configurer des rappels

L'objet de configuration contient un ensemble de rappels appelés à différents moments au cours du flux d'authentification. Vous pouvez ainsi personnaliser davantage l'UI. Les hooks suivants sont disponibles :

selectTenantUiShown() Déclenché lorsque l'UI permettant de sélectionner un locataire s'affiche. Utilisez-le si vous souhaitez modifier l'UI avec un titre ou un thème personnalisé.
signInUiShown(tenantId) Déclenché lorsqu'un locataire est sélectionné et que l'UI permettant à l'utilisateur de saisir ses identifiants s'affiche. Utilisez-le si vous souhaitez modifier l'UI avec un titre ou un thème personnalisé.
beforeSignInSuccess(user) Déclenché avant que la connexion soit accomplie. Utilisez-le pour modifier un utilisateur connecté avant de le rediriger vers la ressource IAP.

L'exemple de code suivant montre comment mettre en œuvre ces rappels :

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

Initialiser la bibliothèque

Une fois que vous avez créé un objet de configuration, procédez comme suit pour initialiser la bibliothèque sur votre page d'authentification :

  1. Créez un conteneur HTML dans lequel afficher l'UI.

    <!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. Créez une instance FirebaseUiHandler à afficher dans le conteneur HTML, puis transmettez-lui l'élément config que vous avez créé.

    const configs = {
      // ...
    }
    const handler = new firebaseui.auth.FirebaseUiHandler(
      '#firebaseui-auth-container', configs);
    
  3. Créez une instance Authentication, transmettez-lui le gestionnaire et appelez start().

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

Déployez votre application et accédez à la page d'authentification. Une UI de connexion contenant vos locataires et vos fournisseurs doit s'afficher.

Étape suivante