Questo articolo mostra come creare una pagina di autenticazione personalizzata utilizzando identità esterne e IAP. La creazione di questa pagina ti consente di controllare completamente il flusso di autenticazione e l'esperienza utente.
Se non devi personalizzare completamente la tua UI, puoi lasciare che IAP ospiti una pagina di accesso per te o utilizzare FirebaseUI per un'esperienza più semplice.
Panoramica
Per creare una pagina di autenticazione personalizzata, segui questi passaggi:
- Abilita identità esterne. Seleziona Fornirò la mia opzione UI durante la configurazione.
- Installa la libreria
gcip-iap. - Configura la UI implementando l'interfaccia
AuthenticationHandler. La pagina di autenticazione deve gestire i seguenti scenari:- Selezione del tenant
- Autorizzazione utente
- Accesso utente
- Gestione degli errori
- (Facoltativo) Personalizza la pagina di autenticazione con funzionalità aggiuntive, come barre di avanzamento, pagine di disconnessione ed elaborazione degli utenti.
- Testa la tua 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 astrae le comunicazioni tra
l'applicazione, IAP e Identity Platform. In questo modo puoi personalizzare l'intero flusso di autenticazione senza dover gestire gli scambi sottostanti tra l'interfaccia utente e IAP.
Utilizza le importazioni corrette per la tua versione dell'SDK:
gcip-iap v0.1.4 o versioni precedenti
// 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 partire dalla versione v1.0.0, gcip-iap richiede la dipendenza peer firebase v9 o versioni successive.
Se esegui la migrazione a gcip-iap v1.0.0 o versioni successive, completa le seguenti
azioni:
- Aggiorna la versione di
firebasenel filepackage.jsonalla versione 9.6.0 o successive. - Aggiorna le istruzioni di importazione
firebasecome 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 modifiche aggiuntive al codice.
gcip-iap v2.0.0
A partire dalla versione v2.0.0, gcip-iap richiede la riscrittura dell'applicazione UI personalizzata utilizzando il formato SDK modulare. Se esegui la migrazione a gcip-iap v2.0.0 o versioni successive, completa le seguenti azioni:
- Aggiorna la versione di
firebasenel filepackage.jsonalla versione 9.8.3 o successive. - Aggiorna le istruzioni di importazione
firebasecome 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 la UI, 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(). Puoi implementare questo metodo per scegliere un tenant in modo programmatico o visualizzare un'interfaccia utente in modo che l'utente possa selezionarne uno.
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'email inserita dall'utente.
Se hai più tenant nel tuo progetto, devi selezionarne uno prima di poter autenticare un utente. Se hai un solo tenant o utilizzi
l'autenticazione a livello di progetto, non devi 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 personalizzata, anonima e con numero di telefono non sono supportati per il multi-tenancy.
Selezione programmatica dei tenant
Per selezionare un tenant in modo programmatico, utilizza il contesto corrente. La classe
Authentication contiene getOriginalURL() che restituisce l'URL
a cui l'utente stava accedendo prima dell'autenticazione.
Utilizza questo campo 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);
});
}
Consentire agli utenti di selezionare i tenant
Per consentire all'utente di selezionare un tenant, visualizza un elenco di tenant e chiedi all'utente di sceglierne uno o di inserire il proprio indirizzo email e poi trovare 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
Dopo aver scelto un fornitore, implementa getAuth() per restituire un'istanza Auth,
corrispondente alla chiave API e all'ID tenant forniti. Se non viene fornito alcun ID tenant, utilizza i provider di identità a livello di progetto.
getAuth() tiene traccia della posizione in cui è archiviato l'utente corrispondente alla
configurazione fornita. Consente inoltre di aggiornare
in modo silenzioso il token ID Identity Platform di un utente autenticato in precedenza
senza richiedere all'utente di inserire nuovamente le credenziali.
Se utilizzi più risorse IAP con tenant diversi, ti consigliamo di utilizzare un'istanza di autenticazione univoca per ogni risorsa. In questo modo, più risorse con configurazioni diverse possono utilizzare la stessa pagina di autenticazione. Consente inoltre a più utenti di accedere contemporaneamente senza disconnettere l'utente precedente.
Di seguito è riportato un esempio di implementazione di 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
Per gestire l'accesso, implementa startSignIn(), mostra un'interfaccia utente per l'autenticazione dell'utente e poi restituisci un UserCredential per l'utente che ha eseguito l'accesso al termine dell'operazione.
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 accedere 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 passaggio di un suggerimento di accesso 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.
});
});
}
Per saperne di più, consulta Autenticazione con multi-tenancy.
Gestione degli errori
Per mostrare messaggi di errore agli utenti o tentare il recupero da errori come i timeout di rete,
implementa handleError().
Nell'esempio riportato sotto viene implementato 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 di IAP che possono
essere restituiti. Identity Platform può anche restituire errori. Consulta la documentazione relativa a
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 |
La mancata autenticazione della richiesta è dovuta a un token OAuth mancante, non valido o scaduto. |
permission-denied |
Il client non dispone di autorizzazioni sufficienti o l'interfaccia utente è ospitata su un dominio non autorizzato. |
not-found . |
La risorsa specificata non è stata trovata. |
aborted |
Conflitto di concorrenza, 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 di 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 |
Torna all'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 decriptografia del token ID GCIP. |
gcip-id-token-unescape-error |
L'escape 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 disconnessione.
Visualizzazione di una UI 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,
implementa showProgressBar() e hideProgressBar().
Disconnessione degli utenti
In alcuni casi, potresti voler consentire agli utenti di disconnettersi da tutte le sessioni attive che condividono lo stesso URL di autenticazione.
Dopo che un utente ha eseguito la disconnessione, potrebbe non esserci un URL a cui reindirizzarlo.
Ciò si verifica in genere quando un utente esce da tutti i tenant associati a una
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,
quando un utente esce viene visualizzata una pagina vuota.
Elaborazione degli utenti
Per modificare un utente che ha eseguito l'accesso prima di reindirizzare alla risorsa IAP,
implementa processUser().
Puoi utilizzare questo metodo per:
- Link ad altri fornitori.
- Aggiorna il profilo dell'utente.
- Chiedi all'utente dati aggiuntivi dopo la registrazione.
- Elabora i token di accesso OAuth restituiti da
getRedirectResult()dopo aver chiamatosignInWithRedirect().
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 vengano riflesse nelle rivendicazioni del token ID propagate da IAP alla tua app, devi forzare l'aggiornamento 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;
});
}
Test dell'interfaccia utente
Dopo aver creato una classe che implementa AuthenticationHandler, puoi
utilizzarla per creare una nuova istanza di Authentication e avviarla:
// 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 visualizzare la tua UI di accesso personalizzata.
Passaggi successivi
- Scopri come accedere in modo programmatico a risorse non Google.
- Scopri di più sulla gestione delle sessioni.
- Scopri come funzionano le identità esterne con IAP.