Benutzerdefinierte Anmeldeseite erstellen

Damit externe Identitäten mit Identity-Aware Proxy (IAP) verwendet werden können, benötigt Ihre Anwendung eine Anmeldeseite. IAP leitet Nutzer auf diese Seite weiter, damit sie sich authentifizieren, bevor sie auf sichere Ressourcen zugreifen können.

In diesem Artikel erfahren Sie, wie Sie eine Authentifizierungsseite von Grund auf neu erstellen. Das Erstellen dieser Seite selbst ist komplex, bietet Ihnen jedoch die vollständige Kontrolle über den Authentifizierungsablauf und die Nutzererfahrung.

Wenn Sie Ihre Benutzeroberfläche nicht vollständig anpassen müssen, können Sie IAP eine Anmeldeseite für Sie hosten lassen oder stattdessen FirebaseUI verwenden, um Ihren Code zu vereinfachen.

Hinweis

Aktivieren Sie externe Identitäten und wählen Sie bei der Einrichtung die Option I'll provide my own UI aus.

gcip-iap-Bibliothek installieren

Das NPM-Modul gcip-iap abstrahiert die Kommunikation zwischen Ihrer Anwendung, IAP und Identity Platform.

Wir empfehlen dringend, die Bibliothek zu verwenden. Sie können damit den gesamten Authentifizierungsablauf konfigurieren, ohne sich um die zugrunde liegenden Austauschvorgänge zwischen UI und IAP Gedanken machen zu müssen.

Fügen Sie die Bibliothek als Abhängigkeit so hinzu:

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

AuthenticationHandler implementieren

Das Modul gcip-iap definiert eine Schnittstelle mit dem Namen AuthenticationHandler. Die Bibliothek ruft die Methoden automatisch zum richtigen Zeitpunkt für die Authentifizierung auf. Die Schnittstelle sieht so aus:

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

Zur Anpassung der UI müssen Sie eine benutzerdefinierte Klasse erstellen, die die Schnittstelle implementiert. Für die Auswahl des verwendeten JavaScript-Frameworks gibt es keine Einschränkungen. Die folgenden Abschnitte enthalten zusätzliche Informationen zum Erstellen der einzelnen Methoden.

Mandant auswählen

Wenn Sie die Mehrinstanzenfähigkeit nutzen, muss ein Mandant ausgewählt werden, bevor ein Nutzer authentifiziert werden kann. Wenn Sie nur einen einzelnen Mandanten oder die Authentifizierung auf Projektebene verwenden, können Sie diesen Schritt überspringen.

IAP unterstützt die gleichen Anbieter wie Identity Platform:

  • E-Mail-Adresse und Passwort
  • OAuth (Google, Facebook, Twitter, GitHub, Microsoft usw.)
  • SAML
  • OIDC
  • Telefonnummer
  • Benutzerdefiniert
  • Anonym

Beachten Sie, dass bei Mehrinstanzenfähigkeit die Authentifizierung durch Telefonnummer sowie die benutzerdefinierte und die anonyme Authentifizierung nicht unterstützt werden.

Zur Auswahl eines Mandanten ruft die Bibliothek den Callback selectTenant() auf. Durch Implementieren dieser Methode kann ein Mandant programmatisch ausgewählt oder eine UI angezeigt werden, mit der der Nutzer selbst einen Mandanten auswählen kann.

Wenn Sie einen Mandanten programmatisch auswählen möchten, nutzen Sie den aktuellen Kontext. Die Klasse Authentication enthält eine getOriginalURL()-Methode, die die URL zurückgibt, die der Nutzer aufgerufen hat, bevor er sich authentifizieren muss. Damit können Sie eine Übereinstimmung über eine Liste der zugehörigen Mandanten ermitteln.

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

Wenn Sie eine UI nutzen möchten, gibt es verschiedene Möglichkeiten für das Festlegen des Anbieters, der verwendet werden soll. Sie können beispielsweise eine Liste der Mandanten anzeigen lassen, aus der der Nutzer einen Mandanten auswählen muss, oder Sie fordern den Nutzer auf, seine E-Mail-Adresse einzugeben, um dann eine Übereinstimmung anhand der Domain zu ermitteln.

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

Unabhängig vom gewählten Vorgehen wird mit dem zurückgegebenen Objekt SelectedTenantInfo später der Authentifizierungsvorgang ausgeführt. Es enthält die ID des ausgewählten Mandanten, alle Anbieter-IDs und die E-Mail-Adresse, die der Nutzer eingegeben hat.

Auth-Objekt abrufen

Wenn ein Anbieter festgelegt ist, müssen Sie eine Möglichkeit zum Abrufen eines Auth-Objekt festlegen. Implementieren Sie dazu den Callback getAuth(), mit dem eine firebase.auth.Auth-Instanz zurückgegeben wird, die dem bereitgestellten API-Schlüssel und der Mandanten-ID entspricht. Wenn keine Mandanten-ID verfügbar ist, müssen Sie stattdessen Identitätsanbieter auf Projektebene verwenden.

Mit getAuth() lässt sich feststellen, wo der Nutzer, der der angegebenen Konfiguration entspricht, gespeichert ist. Außerdem ist es damit möglich, das Identity Platform-ID-Token eines zuvor authentifizierten Nutzers im Hintergrund zu aktualisieren, ohne dass der Nutzer seine Anmeldedaten noch einmal eingeben muss.

Wenn Sie mehrere IAP-Ressourcen mit unterschiedlichen Mandanten nutzen, empfehlen wir die Verwendung einer eindeutigen Auth-Instanz für jede Ressource. Dadurch können Sie mehrere Ressourcen mit unterschiedlichen Konfigurationen die gleiche Authentifizierungsseite nutzen. Außerdem bietet es mehreren Nutzern die Möglichkeit, sich gleichzeitig anzumelden, ohne dass der vorherige Nutzer abgemeldet werden muss.

Im Folgenden finden Sie ein Beispiel für die Implementierung von 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;
}

Nutzer anmelden

Für das Verarbeiten einer Anmeldung implementieren Sie den Callback startSignIn(). Damit wird eine UI für den Nutzer zur Authentifizierung angezeigt und nach der Authentifizierung ein UserCredential-Wert für den angemeldeten Nutzer zurückgegeben.

In einer Umgebung mit mehreren Mandanten können die verfügbaren Authentifizierungsmethoden aus SelectedTenantInfo ermittelt werden, sofern diese Variable angegeben wurde. Sie enthält die Informationen, die vom Callback selectTenant() zurückgegeben werden.

Das folgende Beispiel zeigt, wie Sie startSignIn() für einen vorhandenen Nutzer mit einer E-Mail-Adresse und einem Passwort implementieren können:

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

Sie haben auch die Möglichkeit, Nutzer mit einem Föderationsanbieter wie SAML oder OIDC über ein Pop-up-Fenster oder eine Weiterleitung anzumelden:

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

Einige OAuth-Anbieter unterstützen die Übergabe eines Hinweises für die Anmeldung:

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

Weitere Informationen zur Authentifizierung von Nutzern finden Sie in der Identity Platform-Dokumentation.

Nutzer verarbeiten

Mit der optionalen Methode processUser() können Sie einen angemeldeten Nutzer ändern, bevor er zurück zur IAP-Ressource geleitet wird. Damit sind folgende Aktionen möglich:

  • Mit weiteren Anbietern verknüpfen
  • Nutzerprofil aktualisieren
  • Nutzer nach der Registrierung zur Eingabe zusätzlicher Daten auffordern
  • OAuth-Zugriffstoken verarbeiten, die von getRedirectResult() nach dem Aufruf von signInWithRedirect() zurückgegeben werden

Im Folgenden finden Sie ein Beispiel für die Implementierung von 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;
  });
}

Wenn Änderungen an einem Nutzer in den ID-Token-Anforderungen berücksichtigt werden sollen, die von IAP an Ihre Anwendung weitergegeben werden, müssen Sie die Aktualisierung des Tokens erzwingen:

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

Fortschrittsanzeige darstellen

Durch Implementieren der optionalen Callbacks showProgressBar() und hideProgressBar() wird Nutzern ein benutzerdefinierter Fortschrittsverlauf angezeigt, wenn das gcip-iap-Modul lang andauernde Netzwerkaufgaben ausführt.

Fehlerbehebung

handleError() ist ein optionaler Callback für die Fehlerbehandlung. Durch Implementieren dieses Callbacks werden Nutzern Fehlermeldungen angezeigt oder es wird versucht, bestimmte Fehler (z. B. eine Netzwerkzeitüberschreitung) automatisch zu beheben.

Das folgende Beispiel zeigt, wie Sie handleError() implementieren können:

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

In der folgenden Tabelle sind die spezifischen IAP-Fehlercodes aufgeführt, die angezeigt werden können. Auch über Identity Platform können Fehler ausgegeben werden. Informationen dazu finden Sie in der Dokumentation zu firebase.auth.Auth.

Fehlercode Beschreibung
invalid-argument Der Client hat ein ungültiges Argument angegeben.
failed-precondition Die Anfrage kann im aktuellen Systemzustand nicht ausgeführt werden.
out-of-range Der Client hat einen ungültigen Bereich angegeben.
unauthenticated Die Anfrage wurde aufgrund eines fehlenden, ungültigen oder abgelaufenen OAuth-Tokens nicht authentifiziert.
permission-denied Der Client hat keine ausreichende Berechtigungen oder die UI wird in einer nicht autorisierten Domain gehostet.
not-found Die angegebene Ressource wurde nicht gefunden.
aborted Es ist ein Konflikt aufgrund von gleichzeitig ausgeführten Aktionen aufgetreten, beispielsweise ein Read-Modify-Write-Konflikt.
already-exists Die Ressource, die ein Client erstellen möchte, ist bereits vorhanden.
resource-exhausted Es wurde entweder das Ressourcenkontingent überschritten oder das Ratenlimit erreicht.
cancelled Die Anfrage wurde vom Client abgebrochen.
data-loss Es ist ein dauerhafter Datenverlust oder Datenkorruption aufgetreten.
unknown Unbekannter Serverfehler.
internal Interner Serverfehler.
not-implemented Die API-Methode wurde vom Server nicht implementiert.
unavailable Dienst ist nicht verfügbar.
restart-process Sie müssen die URL, über die Sie auf diese Seite weitergeleitet wurden, noch einmal aufrufen und den Authentifizierungsprozess neu starten.
deadline-exceeded Die Frist der Anfrage wurde überschritten.
authentication-uri-fail Authentifizierungs-URI konnte nicht generiert werden.
gcip-token-invalid Ein ungültiges GCIP-ID-Token wurde angegeben.
gcip-redirect-invalid Ungültige Weiterleitungs-URL.
get-project-mapping-fail Fehler beim Abrufen der Projekt-ID.
gcip-id-token-encryption-error Fehler bei der GCIP-ID-Token-Verschlüsselung.
gcip-id-token-decryption-error Fehler bei der Entschlüsselung des GCIP-ID-Tokens.
gcip-id-token-unescape-error Fehler beim Aufheben der Escapezeichen für die websichere Base64-Codierung.
resource-missing-gcip-sign-in-url GCIP-Authentifizierungs-URL für die angegebene IAP-Ressource ist nicht vorhanden.

Nutzer abmelden

In einigen Fällen kann es sinnvoll sein, Nutzern das Abmelden von allen aktuellen Sitzungen zu erlauben, die die gleiche Authentifizierungs-URL nutzen.

Möglicherweise ist dann aber keine URL vorhanden, zu der der Nutzer nach der Abmeldung zurückgeleitet werden kann. Dies tritt häufig dann auf, wenn sich ein Nutzer von allen mit einer Anmeldeseite verknüpften Mandanten abmeldet. Implementieren Sie in diesem Fall den Callback completeSignOut(). Eine Nachricht zeigt dann an, dass der Nutzer erfolgreich abgemeldet wurde. Ohne Callback wird eine leere Seite dargestellt.

Benutzerdefinierte UI verwenden

Wenn Sie eine Klasse erstellt haben, die AuthenticationHandler implementiert, können Sie damit eine neue Authentication-Instanz erstellen und starten.

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

Stellen Sie Ihre Anwendung bereit und rufen Sie die Authentifizierungsseite auf. Anschließend sollte Ihre benutzerdefinierte Anmeldungs-UI angezeigt werden.

Nächste Schritte