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 mendapatkan pengalaman yang lebih sederhana.

Ringkasan

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

  1. Aktifkan identitas eksternal. Pilih Saya akan memberikan opsi UI sendiri selama penyiapan.
  2. Instal library gcip-iap.
  3. Konfigurasikan UI dengan mengimplementasikan antarmuka AuthenticationHandler. Halaman autentikasi Anda harus menangani skenario berikut:
    • Pemilihan penyewa
    • 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 dasar 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 dari 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 mengimplementasikan metode ini untuk memilih tenant secara terprogram, atau menampilkan UI sehingga pengguna dapat memilihnya sendiri.

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

Jika ada beberapa tenant dalam project, Anda harus memilih satu tenant sebelum dapat mengautentikasi pengguna. Jika hanya memiliki satu tenant, atau menggunakan autentikasi level project, Anda tidak perlu mengimplementasikan selectTenant().

IAP mendukung penyedia yang sama dengan Identity Platform, seperti:

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

Jenis autentikasi dengan 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 metode 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 untuk memilih tenant

Untuk memungkinkan pengguna memilih tenant, tampilkan daftar tenant dan minta pengguna memilih satu, atau minta mereka memasukkan alamat email, lalu menemukan 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 mengembalikan instance Auth, yang sesuai dengan kunci API dan ID tenant yang diberikan. Jika ID tenant tidak diberikan, gunakan penyedia identitas level project.

getAuth() melacak tempat pengguna yang sesuai dengan konfigurasi yang diberikan disimpan. Hal ini juga memungkinkan Anda memperbarui token ID Identity Platform milik pengguna yang telah diautentikasi sebelumnya secara diam-diam 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 berbeda menggunakan halaman autentikasi yang sama. Tindakan ini juga memungkinkan beberapa pengguna login secara bersamaan tanpa membuat pengguna sebelumnya logout.

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 proses login, implementasikan startSignIn(), tampilkan UI untuk autentikasi pengguna, lalu tampilkan UserCredential untuk pengguna yang sudah login setelah selesai.

Dalam lingkungan multi-tenant, Anda dapat menentukan metode autentikasi yang tersedia dari SelectedTenantInfo, jika metode tersebut 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 memproses login pengguna 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.
      });
    });
}

Baca bagian Mengautentikasi dengan multi-tenancy untuk mengetahui informasi selengkapnya.

Menangani error

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

Contoh berikut mengimplementasikan 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 diizinkan.
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 Kunjungi 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 ID project.
gcip-id-token-encryption-error Terjadi error pada enkripsi token ID GCIP.
gcip-id-token-decryption-error Error dekripsi token ID GCIP.
gcip-id-token-unescape-error Unescape base64 keamanan web gagal.
resource-missing-gcip-sign-in-url URL autentikasi GCIP untuk resource IAP yang ditentukan tidak ada.

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 mengeksekusi tugas jaringan yang berjalan lama, implementasikan showProgressBar() dan hideProgressBar().

Membuat pengguna logout

Dalam beberapa kasus, Anda dapat mengizinkan pengguna untuk 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 sudah login sebelum beralih ke resource IAP, terapkan processUser().

Anda dapat menggunakan metode ini untuk melakukan hal berikut:

  • Tautkan 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 ingin perubahan apa pun pada pengguna tercermin dalam klaim token ID yang disebarkan oleh IAP ke aplikasi, 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 mengimplementasikan AuthenticationHandler, Anda dapat menggunakannya untuk membuat instance Authentication baru, lalu 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