Creazione di una pagina di accesso con FirebaseUI

Per utilizzare le identità esterne con Identity-Aware Proxy (IAP), la tua app richiede una pagina di accesso. IAP indirizzerà gli utenti a questa pagina per l'autenticazione prima che possano accedere alle risorse protette.

Questo articolo illustra come creare una pagina di autenticazione utilizzando FirebaseUI, una libreria JavaScript open source. FirebaseUI fornisce elementi personalizzabili che consentono di ridurre il codice boilerplate e gestisce i flussi per l'accesso degli utenti con un'ampia gamma di provider di identità.

Per iniziare più velocemente, consenti a IAP di ospitare l'interfaccia utente. In questo modo puoi provare le identità esterne senza scrivere altro codice. Per scenari più avanzati, puoi anche creare la tua pagina di accesso da zero. È un'opzione più complessa, ma che ti offre il controllo completo del flusso di autenticazione e dell'esperienza utente.

Prima di iniziare

Abilita le identità esterne e seleziona l'opzione I' I will fornisci my own UI (Fornisci la mia interfaccia utente) durante la configurazione.

Installazione delle librerie

Installa le librerie gcip-iap, firebase e firebaseui. Il modulo gcip-iap astrae le comunicazioni tra la tua app, IAP e Identity Platform. Le librerie firebase e firebaseui forniscono gli elementi costitutivi della tua UI di autenticazione.

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

Tieni presente che il modulo gcip-iap non è disponibile tramite CDN.

Dopodiché puoi import i moduli nei file di origine:

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

Le istruzioni precedenti riguardano la versione 0.1.4 o versioni precedenti di gcip-iap.

A partire dalla versione v1.0.0, gcip-iap richiede una dipendenza pari a firebase v9 o superiore. Se stai eseguendo la migrazione a gcip-iap v1.0.0 o versioni successive, completa le seguenti azioni:

  • Aggiorna le versioni firebase e firebaseui del file package.json alle versioni 9.6.0 e 6.0.0 o successive.
  • Aggiorna le istruzioni di importazione di firebase come segue.

Non sono necessarie ulteriori modifiche al codice:

// 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.
import * as ciap from 'gcip-iap';

Per ulteriori opzioni di installazione, incluso l'utilizzo di versioni localizzate delle librerie, consulta le istruzioni su GitHub.

Configurazione dell'applicazione

FirebaseUI utilizza un oggetto di configurazione che specifica i tenant e i provider da usare per l'autenticazione. Una configurazione completa può essere molto lunga e avere un aspetto simile al seguente:

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

Le seguenti sezioni forniscono indicazioni su come configurare alcuni campi specifici per IAP. Per esempi sull'impostazione di altri campi, consulta lo snippet di codice riportato sopra o la documentazione di FirebaseUI su GitHub.

Impostazione della chiave API

Una configurazione tipica inizia con una chiave API per il progetto:

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

Nella maggior parte dei casi, è sufficiente specificare una singola chiave API. Tuttavia, se vuoi utilizzare un unico URL di autenticazione in più progetti, puoi includere più chiavi API:

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

Ottenere il dominio di autenticazione

Imposta il campo authdomain sul dominio di cui è stato eseguito il provisioning per facilitare l'accesso federato. Puoi recuperare questo campo dalla pagina di Identity Platform in Google Cloud Console.

Specifica degli ID tenant

Una configurazione richiede un elenco di tenant e provider con cui gli utenti possono eseguire l'autenticazione.

Ogni tenant è identificato dal suo ID. Se utilizzi l'autenticazione a livello di progetto (senza tenant), utilizza invece l'identificatore _ speciale come chiave API. Ad esempio:

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

Puoi anche specificare una configurazione tenant con caratteri jolly utilizzando l'operatore *. Questo tenant viene utilizzato come riserva se non viene trovato alcun ID corrispondente.

Configurazione dei provider tenant

Ogni tenant ha i propri provider, che vengono specificati nel campo signInOptions:

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

Vedi Configurazione dei provider di accesso nella documentazione di FirebaseUI per scoprire come configurare i provider.

Oltre ai passaggi descritti nella documentazione di FirebaseUI, esistono diversi campi specifici di IAP che dipendono dalla modalità di selezione del tenant che scegli. Per ulteriori informazioni su questi campi, consulta la sezione successiva.

Scelta di una modalità di selezione tenant

Gli utenti possono selezionare un tenant in due modi: modalità basata su opzioni o modalità primo identificatore.

In modalità opzioni, l'utente inizia selezionando un tenant da un elenco, quindi inserisce il proprio nome utente e la password. In modalità identificatore, l'utente inserisce prima l'indirizzo email. Il sistema seleziona quindi automaticamente il primo tenant con un provider di identità che corrisponde al dominio dell'email.

Per utilizzare la modalità Opzioni, imposta displayMode su optionFirst. Dovrai quindi fornire informazioni di configurazione per il pulsante di ogni tenant, inclusi displayName, buttonColor e iconUrl. È inoltre possibile fornire un facoltativo fullLabel per sostituire l'intera etichetta del pulsante invece che solo il nome visualizzato.

Di seguito è riportato un esempio di tenant configurato per l'utilizzo della modalità Opzioni:

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

Per utilizzare la modalità identificatore, ogni opzione di accesso deve specificare un campo hd che indichi il dominio supportato. Può essere un'espressione regolare (come /@example\.com$/) o la stringa di dominio (ad esempio example.com).

Il codice seguente mostra un tenant configurato per l'uso della modalità identificatore:

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

Attivazione del reindirizzamento immediato

Se l'app supporta un solo provider di identità, l'impostazione immediateFederatedRedirect su true salterà l'UI di accesso e reindirizzerà l'utente direttamente al provider.

Configurazione dei callback

L'oggetto di configurazione contiene un insieme di callback richiamati in vari punti del flusso di autenticazione. Ciò consente di personalizzare ulteriormente l'interfaccia utente. Sono disponibili i seguenti hook:

selectTenantUiShown() Si attiva quando viene mostrata l'UI per selezionare un tenant. Utilizza questa opzione se vuoi modificare l'interfaccia utente con un titolo o un tema personalizzati.
signInUiShown(tenantId) Si attiva quando è selezionato un tenant e viene mostrata l'UI per l'utente in cui inserire le credenziali. Utilizza questa opzione se vuoi modificare l'interfaccia utente con un titolo o un tema personalizzati.
beforeSignInSuccess(user) Si attiva prima del completamento dell'accesso. Utilizza questa opzione per modificare un utente che ha eseguito l'accesso prima di reindirizzare alla risorsa IAP.

Il seguente codice di esempio mostra come implementare questi callback:

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

Inizializzazione della libreria

Dopo aver creato un oggetto di configurazione, segui questi passaggi per inizializzare la libreria nella pagina di autenticazione:

  1. Crea un contenitore HTML in cui visualizzare l'interfaccia utente.

    <!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 un'istanza FirebaseUiHandler da visualizzare nel container HTML, quindi passa l'elemento config che hai creato.

    const configs = {
      // ...
    }
    const handler = new firebaseui.auth.FirebaseUiHandler(
      '#firebaseui-auth-container', configs);
    
  3. Crea una nuova istanza Authentication, passaci il gestore e chiama start().

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

Eseguire il deployment dell'applicazione e passare alla pagina di autenticazione. Dovrebbe essere visualizzata un'interfaccia utente di accesso contenente i tuoi tenant e provider.

Passaggi successivi