Membuat halaman login kustom

Artikel ini menunjukkan cara membuat halaman autentikasi Anda sendiri menggunakan identitas eksternal dan IAP. Membuat halaman ini sendiri memberi Anda kontrol penuh atas alur autentikasi dan pengalaman pengguna.

Jika tidak perlu menyesuaikan UI sepenuhnya, Anda dapat mengizinkan IAP menghosting halaman login untuk Anda, atau menggunakan FirebaseUI untuk pengalaman yang lebih sederhana.

Ringkasan

Untuk membuat halaman autentikasi Anda sendiri, ikuti langkah-langkah berikut:

  1. Aktifkan identitas eksternal. Pilih Saya akan menyediakan opsi UI saya sendiri selama penyiapan.
  2. Instal library gcip-iap.
  3. Konfigurasi UI dengan menerapkan antarmuka AuthenticationHandler. Halaman autentikasi Anda harus menangani skenario berikut:
    • Pemilihan tenant
    • Otorisasi pengguna
    • Login pengguna
    • Penanganan error
  4. Opsional: Sesuaikan halaman autentikasi Anda dengan fitur tambahan, seperti status progres, halaman logout, dan pemrosesan pengguna.
  5. Uji UI Anda.

Menginstal library gcip-iap

Untuk menginstal library gcip-iap, jalankan perintah berikut:

npm install gcip-iap --save

Modul NPM gcip-iap memisahkan komunikasi antara aplikasi, IAP, dan Identity Platform Anda. Hal ini memungkinkan Anda menyesuaikan seluruh alur autentikasi tanpa harus mengelola pertukaran yang mendasarinya antara UI dan IAP.

Gunakan impor yang benar untuk versi SDK Anda:

gcip-iap v0.1.4 atau yang lebih lama

// 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 hingga v1.1.0

Mulai versi v1.0.0, gcip-iap memerlukan dependensi peer firebase v9 atau yang lebih baru. Jika Anda bermigrasi ke gcip-iap v1.0.0 atau yang lebih baru, selesaikan tindakan berikut:

  • Update versi firebase di file package.json Anda ke v9.6.0+.
  • Perbarui pernyataan impor firebase sebagai berikut:
// Import Firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import the gcip-iap module.
import * as ciap from 'gcip-iap';

Tidak diperlukan perubahan kode tambahan.

gcip-iap v2.0.0

Mulai versi v2.0.0, gcip-iap memerlukan penulisan ulang aplikasi UI kustom Anda menggunakan format SDK modular. Jika Anda bermigrasi ke gcip-iap v2.0.0 atau yang lebih baru, selesaikan tindakan berikut:

  • Update versi firebase di file package.json Anda ke v9.8.3+.
  • Perbarui pernyataan impor firebase sebagai berikut:
  // Import Firebase modules.
  import { initializeApp } from 'firebase/app';
  import { getAuth, GoogleAuthProvider } 'firebase/auth';
  // Import the gcip-iap module.
  import * as ciap from 'gcip-iap';

Mengonfigurasi UI

Untuk mengonfigurasi UI, buat class kustom yang mengimplementasikan antarmuka 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;
}

Selama autentikasi, library akan otomatis memanggil metode AuthenticationHandler.

Memilih tenant

Untuk memilih tenant, terapkan selectTenant(). Anda dapat menerapkan metode ini untuk memilih tenant secara terprogram, atau menampilkan UI agar pengguna dapat memilihnya sendiri.

Dalam kedua kasus tersebut, library menggunakan objek SelectedTenantInfo yang ditampilkan untuk menyelesaikan alur autentikasi. URL ini berisi ID tenant yang dipilih, ID penyedia apa pun, dan email yang dimasukkan pengguna.

Jika memiliki beberapa tenant dalam project, Anda harus memilih salah satunya sebelum dapat melakukan autentikasi pengguna. Jika hanya memiliki satu tenant, atau menggunakan autentikasi tingkat project, Anda tidak perlu menerapkan selectTenant().

IAP mendukung penyedia yang sama dengan Identity Platform, seperti:

  • Email dan sandi
  • OAuth (Google, Facebook, Twitter, GitHub, Microsoft, dll.)
  • SAML
  • OIDC
  • Nomor telepon
  • Kustom
  • Anonim

Jenis autentikasi nomor telepon, kustom, dan anonim tidak didukung untuk multi-tenancy.

Memilih tenant secara terprogram

Untuk memilih tenant secara terprogram, manfaatkan konteks saat ini. Class Authentication berisi getOriginalURL() yang menampilkan URL yang diakses pengguna sebelum autentikasi.

Gunakan ini untuk menemukan kecocokan dari daftar tenant terkait:

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

Mengizinkan pengguna memilih tenant

Untuk mengizinkan pengguna memilih tenant, tampilkan daftar tenant dan minta pengguna untuk memilih salah satunya, atau minta mereka memasukkan alamat email, lalu temukan kecocokan berdasarkan domain:

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

Mengautentikasi pengguna

Setelah Anda memiliki penyedia, terapkan getAuth() untuk menampilkan instance Auth, yang sesuai dengan kunci API dan ID tenant yang diberikan. Jika tidak ada ID tenant yang diberikan, gunakan penyedia identitas tingkat project.

getAuth() melacak tempat pengguna yang sesuai dengan konfigurasi yang diberikan disimpan. Hal ini juga memungkinkan untuk memuat ulang token ID Identity Platform pengguna yang diautentikasi sebelumnya secara otomatis tanpa mengharuskan pengguna memasukkan kembali kredensialnya.

Jika Anda menggunakan beberapa resource IAP dengan tenant yang berbeda, sebaiknya gunakan instance autentikasi unik untuk setiap resource. Hal ini memungkinkan beberapa resource dengan konfigurasi yang berbeda untuk menggunakan halaman autentikasi yang sama. Fitur ini juga memungkinkan beberapa pengguna login secara bersamaan tanpa logout pengguna sebelumnya.

Berikut adalah contoh cara menerapkan 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;
}

Memproses login pengguna

Untuk menangani login, terapkan startSignIn(), tampilkan UI untuk diautentikasi pengguna, lalu tampilkan UserCredential untuk pengguna yang login setelah selesai.

Dalam lingkungan multi-tenant, Anda dapat menentukan metode autentikasi yang tersedia dari SelectedTenantInfo, jika disediakan. Variabel ini berisi informasi yang sama dengan yang ditampilkan oleh selectTenant().

Contoh berikut menunjukkan implementasi startSignIn() untuk pengguna yang ada dengan email dan sandi:

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

Anda juga dapat membuat pengguna login dengan penyedia gabungan, seperti SAML atau OIDC, menggunakan pop-up atau pengalihan:

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

Beberapa penyedia OAuth mendukung penerusan petunjuk login untuk login:

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

Lihat Mengautentikasi dengan multi-tenancy untuk informasi selengkapnya.

Menangani error

Untuk menampilkan pesan error kepada pengguna atau mencoba pemulihan dari error seperti waktu tunggu jaringan habis, terapkan handleError().

Contoh berikut menerapkan 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;
  });
}

Tabel di bawah mencantumkan kode error khusus IAP yang dapat ditampilkan. Identity Platform juga dapat menampilkan error; lihat dokumentasi untuk firebase.auth.Auth.

Kode error Deskripsi
invalid-argument Klien menentukan argumen yang tidak valid.
failed-precondition Permintaan tidak dapat dijalankan dalam status sistem saat ini.
out-of-range Klien menentukan rentang yang tidak valid.
unauthenticated Permintaan tidak diautentikasi karena token OAuth tidak ada, tidak valid, atau sudah tidak berlaku.
permission-denied Klien tidak memiliki izin yang memadai, atau UI dihosting di domain yang tidak sah.
not-found . Resource yang ditentukan tidak ditemukan.
aborted Konflik serentak, seperti konflik baca-ubah-tulis.
already-exists Resource yang klien coba buat sudah ada.
resource-exhausted Kuota resource telah habis atau mencapai pembatasan kapasitas.
cancelled Permintaan dibatalkan oleh klien.
data-loss Kehilangan data atau kerusakan data yang tidak dapat dipulihkan.
unknown Terjadi error yang tidak diketahui pada server.
internal Error server internal.
not-implemented Metode API tidak diimplementasikan oleh server.
unavailable Layanan tidak tersedia.
restart-process Buka kembali URL yang mengalihkan Anda ke halaman ini untuk memulai ulang proses autentikasi.
deadline-exceeded Batas waktu permintaan terlampaui.
authentication-uri-fail Gagal membuat URI autentikasi.
gcip-token-invalid Token ID GCIP yang diberikan tidak valid.
gcip-redirect-invalid URL alihan tidak valid.
get-project-mapping-fail Gagal mendapatkan project ID.
gcip-id-token-encryption-error Error enkripsi token ID GCIP.
gcip-id-token-decryption-error Error dekripsi token ID GCIP.
gcip-id-token-unescape-error Penghapusan enkode base64 yang aman bagi web gagal.
resource-missing-gcip-sign-in-url URL autentikasi GCIP tidak ada untuk resource IAP yang ditentukan.

Menyesuaikan UI

Anda dapat menyesuaikan halaman autentikasi dengan fitur opsional seperti status progres dan halaman logout.

Menampilkan UI progres

Untuk menampilkan UI progres kustom kepada pengguna setiap kali modul gcip-iap menjalankan tugas jaringan yang berjalan lama, terapkan showProgressBar() dan hideProgressBar().

Membuat pengguna logout

Dalam beberapa kasus, Anda mungkin ingin mengizinkan pengguna logout dari semua sesi saat ini yang memiliki URL autentikasi yang sama.

Setelah pengguna logout, mungkin tidak ada URL untuk mengalihkan mereka kembali. Hal ini biasanya terjadi saat pengguna logout dari semua tenant yang terkait dengan halaman login. Dalam hal ini, terapkan completeSignOut() untuk menampilkan pesan yang menunjukkan bahwa pengguna berhasil logout. Jika Anda tidak menerapkan metode ini, halaman kosong akan muncul saat pengguna logout.

Memproses pengguna

Untuk mengubah pengguna yang login sebelum mengalihkan ke resource IAP, terapkan processUser().

Anda dapat menggunakan metode ini untuk melakukan hal berikut:

  • Link ke penyedia tambahan.
  • Perbarui profil pengguna.
  • Minta data tambahan kepada pengguna setelah pendaftaran.
  • Memproses token akses OAuth yang ditampilkan oleh getRedirectResult() setelah memanggil signInWithRedirect().

Berikut adalah contoh penerapan 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;
  });
}

Jika Anda ingin perubahan pada pengguna ditampilkan dalam klaim token ID yang disebarkan oleh IAP ke aplikasi Anda, Anda harus memaksa token untuk dimuat ulang:

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

Menguji UI

Setelah membuat class yang menerapkan AuthenticationHandler, Anda dapat menggunakannya untuk membuat instance Authentication baru, dan memulainya:

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

Deploy aplikasi Anda dan buka halaman autentikasi. Anda akan melihat UI login kustom.

Langkah selanjutnya