Per utilizzare identità esterne con Identity-Aware Proxy (IAP), la tua app richiede una pagina di accesso. IAP reindirizzerà gli utenti a questa pagina per autenticarsi prima che possano accedere alle risorse sicure.
Questo articolo spiega come creare una pagina di autenticazione da zero. La creazione indipendente di questa pagina è complessa, ma ti offre un controllo completo sul flusso di autenticazione e sull'esperienza utente.
Se non hai la necessità di personalizzare completamente la tua interfaccia utente, puoi lasciare che sia IAP a ospitare una pagina di accesso per te, oppure a utilizzare FirebaseUI, per semplificare il codice.
Prima di iniziare
Abilita identità esterne e seleziona l'opzione Fornire la mia interfaccia utente durante la configurazione.
Installazione della libreria gcip-iap
Il modulo NPM gcip-iap
nasconde le comunicazioni tra
la tua applicazione, IAP e Identity Platform.
Ti consigliamo vivamente di utilizzare la raccolta. Ti consente di personalizzare l'intero flusso di autenticazione senza preoccuparti delle piattaforme di scambio sottostanti tra l'interfaccia utente e gli IAP.
Includi la libreria come una dipendenza simile alla seguente:
// 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';
Le istruzioni precedenti riguardano gcip-iap
0.1.4 o versioni precedenti.
A partire dalla versione 1.0.0, gcip-iap
richiede la dipendenza peer v9 firebase
o superiore.
Se stai eseguendo la migrazione alla gcip-iap
v1.0.0 o successiva, completa le seguenti azioni:
- Aggiorna la versione
firebase
del filepackage.json
alla versione 9.6.0 o successiva. - Aggiorna le istruzioni di importazione
firebase
come segue.
Non sono necessarie ulteriori modifiche al codice:
// Import firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import gcip-iap module.
import * as ciap from 'gcip-iap';
Implementazione di AuthenticationHandler
Il modulo gcip-iap
definisce un'interfaccia denominata AuthenticationHandler
.
La libreria chiama automaticamente i propri metodi al momento opportuno per gestire
l'autenticazione. L'interfaccia ha il seguente aspetto:
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;
}
Per personalizzare l'interfaccia utente, devi creare una classe personalizzata che implementi l'interfaccia. Non esistono limitazioni relative al framework JavaScript che utilizzi. Le seguenti sezioni forniscono ulteriori informazioni su come creare ogni metodo.
Selezione di un tenant
Se utilizzi la multi-tenancy, devi selezionare un tenant prima di poter autenticare un utente. Se hai un solo tenant o utilizzi l'autenticazione a livello di progetto, puoi saltare questo passaggio.
IAP supporta gli stessi provider di Identity Platform, ad esempio:
- Email e password
- OAuth (Google, Facebook, Twitter, GitHub, Microsoft ecc.)
- SAML
- OIDC
- Numero di telefono
- Personalizzato
- Anonimo
Tieni presente che numero di telefono, autenticazione personalizzata e autenticazione non sono supportate per la modalità multi-tenancy.
Per selezionare un tenant, la libreria richiama il callback selectTenant()
. Puoi implementare questo metodo per scegliere un tenant in modo programmatico o visualizzare una UI in modo che l'utente possa selezionarne una da sola.
Per scegliere un tenant in modo programmatico, sfrutta il contesto attuale. La classe Authentication
contiene un metodo getOriginalURL()
che restituisce l'URL a cui l'utente stava accedendo prima di dover eseguire l'autenticazione. che potrai utilizzare per individuare 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);
});
}
Se scegli di visualizzare un'interfaccia utente, esistono diversi approcci per determinare il provider da utilizzare. Ad esempio, potresti visualizzare un elenco di inquilini e chiedere all'utente di sceglierne uno, oppure puoi inserire il suo indirizzo email e individuare 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,
});
});
});
}
Indipendentemente dal tuo approccio, l'oggetto SelectedTenantInfo
restituito verrà utilizzato in un secondo momento per completare il flusso di autenticazione. Contiene l'ID del tenant selezionato, gli eventuali ID provider e l'email inserita dall'utente.
Recupero dell'oggetto Auth
Dopo avere ottenuto un provider, devi ottenere un modo per ottenere un oggetto Auth
.
Implementa il callback getAuth()
per restituire un'istanza
firebase.auth.Auth
corrispondente alla chiave API e all'ID tenant forniti. Se non viene fornito un ID tenant, deve utilizzare i provider di identità a livello di progetto.
getAuth()
viene utilizzato per monitorare dove viene archiviato l'utente corrispondente alla
configurazione fornita. Consente inoltre di aggiornare automaticamente il token ID Identity Platform di un utente autenticato in precedenza
senza richiedere all'utente di reinserire le proprie credenziali.
Se utilizzi più risorse IAP con tenant diversi, ti consigliamo di utilizzare un'istanza Auth
univoca per ciascuna. In questo modo, più risorse con configurazioni diverse possono utilizzare la stessa pagina di autenticazione. Inoltre, permette a più utenti di accedere contemporaneamente senza disconnettere l'utente precedente.
Di seguito è riportato un esempio di come implementare getAuth()
:
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. This makes it possible to have
// multiple users 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;
}
Accesso degli utenti
Per gestire l'accesso, implementa il callback startSignIn()
. Dovrebbe essere visualizzata un'interfaccia
utente per l'autenticazione, quindi restituire una UserCredential
per l'utente che ha eseguito l'accesso al termine.
In un ambiente multi-tenant, i metodi di autenticazione disponibili possono essere
determinati da SelectedTenantInfo
, se fornito. Questa variabile contiene le stesse informazioni restituite dal callback selectTenant()
.
L'esempio seguente mostra come implementare startSignIn()
per un utente esistente con un indirizzo email e una password:
startSignIn(auth, selectedTenantInfo) {
return new Promise((resolve, reject) => {
// Show 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 user for email and password.
// 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 error message.
});
});
});
}
Puoi anche accedere agli utenti con un provider federato, come SAML o OIDC, utilizzando un popup o un reindirizzamento:
startSignIn(auth, selectedTenantInfo) {
// Show UI to sign-in or sign-up a user.
return new Promise((resolve, reject) => {
// Provide user multiple buttons to sign-in.
// For example sign-in with popup using a SAML provider.
// 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 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);
});
}
Alcuni provider OAuth supportano il passaggio di suggerimenti per l'accesso:
startSignIn(auth, selectedTenantInfo) {
// Show 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.
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 error message.
});
});
}
Consulta la documentazione di Identity Platform per scoprire di più sull'autenticazione degli utenti.
Elaborazione di un utente
Il metodo facoltativo processUser()
consente di modificare un utente che ha eseguito l'accesso prima di reindirizzare alla risorsa IAP. Puoi utilizzarlo 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()
dopo la chiamata asignInWithRedirect()
.
Di seguito è riportato un esempio di implementazione di processUser()
:
processUser(user) {
return lastAuthUsed.getRedirectResult().then(function(result) {
// Save additional data, or ask 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;
});
}
Tieni presente che se vuoi apportare eventuali modifiche a un utente nelle rivendicazioni del token ID propagate da IAP nella tua app, dovrai forzare l'aggiornamento del token:
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;
});
}
Visualizzazione di un'interfaccia utente di avanzamento
Implementa i callback facoltativi showProgressBar()
e hideProgressBar()
per visualizzare un'interfaccia utente di avanzamento personalizzata per l'utente ogni volta che il modulo gcip-iap
esegue attività di rete di lunga durata.
Gestione degli errori
handleError()
è un callback facoltativo per la gestione degli errori. Implementalo
per mostrare messaggi di errore agli utenti o tentare di recuperarli da determinati errori
(come un timeout di rete).
L'esempio seguente mostra come implementare 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 generati. Identity Platform può anche generare 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 |
Consulta l'URL che ti ha reindirizzato a questa pagina per riavviare la procedura 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 ottenere l'ID progetto. |
gcip-id-token-encryption-error |
Errore di crittografia del token ID GCIP. |
gcip-id-token-decryption-error |
Errore di decriptazione del token ID GCIP. |
gcip-id-token-unescape-error |
Annullamento avanzamento Base64 sicuro per il Web non riuscito. |
resource-missing-gcip-sign-in-url |
URL di autenticazione GCIP mancante per la risorsa IAP specificata. |
Disconnessione degli utenti
In alcuni casi, potresti voler consentire agli utenti di uscire da tutte le sessioni in corso che condividono lo stesso URL di autenticazione.
Dopo che un utente si disconnette, potrebbe non esistere alcun URL per reindirizzare l'utente a (questo accade di solito quando un utente si disconnette da tutti i tenant associati a una pagina di accesso). In questo caso, implementa il callback completeSignOut()
per visualizzare un messaggio che indica che l'utente è stato disconnesso. In caso contrario,
verrà visualizzata una pagina vuota.
Utilizzo dell'interfaccia utente personalizzata
Dopo aver creato una classe che implementa AuthenticationHandler
, puoi utilizzarla per creare una nuova istanza Authentication
e avviarla.
// Implement interface AuthenticationHandler.
// const authHandlerImplementation = ....
const ciapInstance = new ciap.Authentication(authHandlerImplementation);
ciapInstance.start();
Esegui il deployment della tua applicazione e vai alla pagina di autenticazione. Dovresti visualizzare la tua interfaccia utente di accesso personalizzata.
Passaggi successivi
- Scopri come accedere alle risorse non Google in modo programmatico.
- Scopri di più sulla gestione delle sessioni.
- Comprendi più a fondo il funzionamento di identità esterne con IAP.