Creazione di una pagina di accesso personalizzata

Questo articolo spiega come creare la tua pagina di autenticazione utilizzando identità esterne e IAP. Se crei questa pagina autonomamente, hai il pieno controllo sul flusso di autenticazione e sull'esperienza utente.

Se non devi personalizzare completamente la tua UI, puoi IAP ospitano una pagina di accesso per te, oppure usa FirebaseUI per un'esperienza più semplice.

Panoramica

Per creare la tua pagina di autenticazione, segui questi passaggi:

  1. Abilita le identità esterne. Seleziona Fornirò la mia UI durante la configurazione.
  2. Installa la libreria gcip-iap.
  3. Configura l'interfaccia utente implementando l'interfaccia AuthenticationHandler. La pagina di autenticazione deve gestire i seguenti scenari:
    • Selezione dell'account utente
    • Autorizzazione utente
    • Accesso utente
    • Gestione degli errori
  4. (Facoltativo) Personalizza la pagina di autenticazione con funzionalità aggiuntive, ad esempio le barre di avanzamento, le pagine di uscita e l'elaborazione degli utenti.
  5. Testa l'UI.

Installazione della libreria gcip-iap

Per installare la libreria gcip-iap, esegui questo comando:

npm install gcip-iap --save

Il modulo NPM gcip-iap esegue l'astrazione delle comunicazioni tra la tua applicazione, l'IAP e Identity Platform. In questo modo puoi personalizzare l'intero flusso di autenticazione senza dover gestire gli scambi sottostanti tra UI e IAP.

Utilizza le importazioni corrette per la versione dell'SDK:

gcip-iap v0.1.4 o precedente

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

Da gcip-iap v1.0.0 a v1.1.0

A partire dalla versione v1.0.0, gcip-iap richiede la dipendenza peer firebase v9 o successiva. Se esegui la migrazione a gcip-iap 1.0.0 o versioni successive, completa le seguenti azioni:

  • Aggiorna la versione di firebase nel file package.json alla versione 9.6.0 o successiva.
  • Aggiorna le istruzioni di importazione firebase come segue:
// Import Firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import the gcip-iap module.
import * as ciap from 'gcip-iap';

Non sono necessarie ulteriori modifiche al codice.

Gciip-iap versione 2.0.0

A partire dalla versione 2.0.0, gcip-iap richiede la riscrittura dell'applicazione UI personalizzata utilizzando il formato SDK modulare. Se stai eseguendo la migrazione a gcip-iap v2.0.0 o versioni successive, completa le seguenti azioni:

  • Aggiorna la versione firebase nel file package.json alla versione 9.8.3 o successive.
  • Aggiorna le istruzioni di importazione firebase come segue:
  // Import Firebase modules.
  import { initializeApp } from 'firebase/app';
  import { getAuth, GoogleAuthProvider } 'firebase/auth';
  // Import the gcip-iap module.
  import * as ciap from 'gcip-iap';

Configurazione dell'interfaccia utente

Per configurare l'interfaccia utente, crea una classe personalizzata che implementi l'interfaccia 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 l'autenticazione, la libreria chiama automaticamente i metodi di AuthenticationHandler.

Selezione dei tenant

Per selezionare un tenant, implementa selectTenant(). Tu puoi implementare questo metodo per scegliere un tenant in modo programmatico, oppure visualizzare una UI in modo che l'utente possa selezionarne una.

In entrambi i casi, la libreria utilizza l'oggetto SelectedTenantInfo restituito per completare il flusso di autenticazione. Contiene l'ID del tenant selezionato, eventuali ID provider e l'indirizzo email inserito dall'utente.

Se nel tuo progetto sono presenti più tenant, devi selezionarne uno prima di poter autenticare un utente. Se hai un solo tenant o utilizzi l'autenticazione a livello di progetto, non è necessario implementare selectTenant().

IAP supporta gli stessi provider di Identity Platform, ad esempio:

  • Email e password
  • OAuth (Google, Facebook, Twitter, GitHub, Microsoft e così via)
  • SAML
  • OIDC
  • Numero di telefono
  • Personalizzato
  • Anonimo

I tipi di autenticazione basata su numero di telefono, personalizzata e anonima non sono supportati per la multitenancy.

Selezione dei tenant tramite programmazione

Per selezionare un tenant in modo programmatico, utilizza il contesto attuale. La La classe Authentication contiene getOriginalURL() che restituisce il valore URL a cui l'utente stava accedendo prima dell'autenticazione.

Utilizza questa opzione per trovare una corrispondenza da un elenco di tenant associati:

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

Consente agli utenti di selezionare i tenant

Per consentire all'utente di selezionare un tenant, visualizza un elenco di tenant e oppure chiedigli di inserire l'indirizzo email e poi individua una corrispondenza in base al 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,
          });
        });
  });
}

Autenticazione degli utenti

Quando hai un provider, implementa getAuth() per restituire un'istanza Auth, corrispondente alla chiave API e all'ID tenant forniti. Se non sono presenti ID tenant i provider di identità a livello di progetto.

getAuth() monitora la posizione in cui l'utente corrisponde al viene archiviata la configurazione specificata. Inoltre, permette di controllare, aggiorna il token ID Identity Platform di un utente autenticato in precedenza senza richiedere all'utente di reinserire le credenziali.

Se utilizzi più risorse IAP con tenant diversi, ti consigliamo di utilizzare un'istanza di autenticazione univoca per ogni risorsa. Ciò consente risorse multiple con configurazioni diverse per utilizzare la stessa autenticazione . Inoltre, consente a più utenti di accedere contemporaneamente senza dover effettuare il logging. l'utente precedente.

Di seguito è riportato un esempio di come implementare 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;
}

Accesso degli utenti in corso...

Per gestire l'accesso, implementa startSignIn(), mostra un UI che l'utente deve eseguire per l'autenticazione e poi restituire un UserCredential per l'utente che ha eseguito l'accesso al completamento.

In un ambiente multi-tenant, puoi determinare i metodi di autenticazione disponibili da SelectedTenantInfo, se è stato fornito. Questa variabile contiene le stesse informazioni restituite da selectTenant().

L'esempio seguente mostra un'implementazione di startSignIn() per un utente esistente con un indirizzo email e una password:

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

Puoi anche far accedere gli utenti con un provider federato, ad esempio SAML o OIDC, utilizzando un popup o un reindirizzamento:

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

Alcuni provider OAuth supportano il trasferimento di un suggerimento per l'accesso:

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 Autenticazione con multi-tenancy per ulteriori informazioni.

Gestione degli errori

Per mostrare messaggi di errore agli utenti o per tentare il ripristino da errori come timeout di rete: implementare handleError().

L'esempio seguente 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;
  });
}

La tabella seguente elenca i codici di errore specifici IAP che possono da restituire. Identity Platform può anche restituire errori; consulta la documentazione per firebase.auth.Auth

Codice di errore Descrizione
invalid-argument Il client ha specificato un argomento non valido.
failed-precondition La richiesta non può essere eseguita nello stato attuale del sistema.
out-of-range Il client ha specificato un intervallo non valido.
unauthenticated Richiesta non autenticata a causa di un token OAuth mancante, non valido o scaduto.
permission-denied Il client non dispone di autorizzazioni sufficienti oppure l'interfaccia utente è ospitata su un dominio non autorizzato.
not-found . La risorsa specificata non è stata trovata.
aborted Conflitto di contemporaneità, ad esempio conflitto di lettura, modifica e scrittura.
already-exists La risorsa che un client ha cercato di creare esiste già.
resource-exhausted Quota di risorse esaurita o vicina alla limitazione della frequenza.
cancelled La richiesta è stata annullata dal client.
data-loss Perdita di dati non recuperabili o danneggiamento dei dati.
unknown Errore sconosciuto del server.
internal Errore interno del server.
not-implemented Metodo API non implementato dal server.
unavailable Servizio non disponibile.
restart-process Visita di nuovo l'URL che ti ha reindirizzato a questa pagina per riavviare il processo di autenticazione.
deadline-exceeded Scadenza richiesta superata.
authentication-uri-fail Impossibile generare l'URI di autenticazione.
gcip-token-invalid Token ID GCIP fornito non valido.
gcip-redirect-invalid URL di reindirizzamento non valido.
get-project-mapping-fail Impossibile recuperare l'ID progetto.
gcip-id-token-encryption-error Errore di crittografia del token ID GCIP.
gcip-id-token-decryption-error Errore di decrittografia del token ID GCIP.
gcip-id-token-unescape-error Annullamento della escape in base64 sicuro per il web non riuscito.
resource-missing-gcip-sign-in-url URL di autenticazione GCIP mancante per la risorsa IAP specificata.

Personalizzazione dell'interfaccia utente

Puoi personalizzare la pagina di autenticazione con funzionalità facoltative come barre di avanzamento e pagine di uscita.

Visualizzazione di un'interfaccia utente di avanzamento

Per mostrare all'utente un'interfaccia utente di avanzamento personalizzata ogni volta che il modulo gcip-iap esegue attività di rete a lunga esecuzione: implementare showProgressBar() e hideProgressBar().

Disconnessione degli utenti

In alcuni casi, potresti voler consentire agli utenti di uscire sessioni che condividono lo stesso URL di autenticazione.

Una volta che un utente esce dal sito o dall'app, potrebbe non esserci alcun URL a cui reindirizzarlo. Questo accade comunemente quando un utente esce da tutti i tenant associati pagina di accesso. In questo caso, implementa completeSignOut() per visualizzare un messaggio che indica che l'utente ha eseguito la disconnessione correttamente. Se non implementi questo metodo, viene visualizzata una pagina vuota quando un utente esce.

Elaborazione utenti

Per modificare un utente che ha eseguito l'accesso prima di reindirizzare alla risorsa IAP, implementare processUser().

Puoi utilizzare questo metodo per:

  • Link a fornitori aggiuntivi.
  • Aggiorna il profilo dell'utente.
  • Chiedi all'utente ulteriori dati dopo la registrazione.
  • Elabora i token di accesso OAuth restituiti da getRedirectResult() in seguito chiamata al numero signInWithRedirect().

Di seguito è riportato un esempio di implementazione di 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;
  });
}

Se vuoi che le modifiche apportate a un utente nelle rivendicazioni del token ID propagato da IAP alla tua app, devi forzare token per aggiornare:

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

Test dell'interfaccia utente

Dopo aver creato un corso che implementa AuthenticationHandler, puoi utilizzalo per creare una nuova istanza Authentication e avviala:

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

Esegui il deployment dell'applicazione e vai alla pagina di autenticazione. Dovresti vedere la UI di accesso personalizzata.

Passaggi successivi