Membuat halaman login dengan FirebaseUI

Untuk menggunakan identitas eksternal dengan Identity-Aware Proxy (IAP), aplikasi Anda memerlukan halaman login. IAP akan mengalihkan pengguna ke halaman ini untuk melakukan autentikasi sebelum mereka dapat mengakses resource aman.

Artikel ini menunjukkan cara membuat halaman autentikasi menggunakan FirebaseUI, library JavaScript open source. FirebaseUI menyediakan elemen yang dapat disesuaikan yang membantu mengurangi kode boilerplate, dan menangani alur untuk memproses login pengguna dengan berbagai penyedia identitas.

Untuk memulai lebih cepat, izinkan IAP menghosting UI untuk Anda. Hal ini memungkinkan Anda mencoba identitas eksternal tanpa menulis kode tambahan. Untuk skenario lanjutan, Anda juga dapat mem-build halaman login Anda sendiri dari awal. Opsi ini lebih kompleks, tetapi memberi Anda kontrol penuh atas alur autentikasi dan pengalaman pengguna.

Sebelum memulai

Aktifkan identitas eksternal, lalu pilih opsi Saya akan menyediakan UI saya sendiri selama penyiapan.

Menginstal library

Instal library gcip-iap, firebase, dan firebaseui. Modul gcip-iap memisahkan komunikasi antara aplikasi, IAP, dan Identity Platform Anda. Library firebase dan firebaseui menyediakan elemen penyusun untuk UI autentikasi Anda.

npm install firebase --save
npm install firebaseui --save
npm install gcip-iap --save

Perhatikan bahwa modul gcip-iap tidak tersedia menggunakan CDN.

Kemudian, Anda dapat import modul dalam file sumber. Gunakan impor yang benar untuk versi SDK Anda:

gcip-iap v0.1.4 atau yang lebih lama

// Import firebase modules.
import * as firebase from "firebase/app";
import "firebase/auth";
// Import firebaseui module.
import * as firebaseui from 'firebaseui'
// Import gcip-iap module.
import * as ciap from 'gcip-iap';

gcip-iap v1.0.0 atau yang lebih baru

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 dan firebaseui dalam file package.json Anda masing-masing ke v9.6.0+ dan v6.0.0+.
  • Perbarui pernyataan impor firebase sebagai berikut:
// Import firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import firebaseui module.
import * as firebaseui from 'firebaseui'
// Import gcip-iap module.

Tidak diperlukan perubahan kode tambahan.

Untuk opsi penginstalan tambahan, termasuk menggunakan versi library yang dilokalkan, lihat petunjuk di GitHub.

Mengonfigurasi aplikasi

FirebaseUI menggunakan objek konfigurasi yang menentukan tenant dan penyedia yang akan digunakan untuk autentikasi. Konfigurasi lengkap bisa sangat panjang, dan mungkin terlihat seperti ini:

// The project configuration.
const configs = {
  // Configuration for project identified by API key API_KEY1.
  API_KEY1: {
    authDomain: 'project-id1.firebaseapp.com',
    // Decide whether to ask user for identifier to figure out
    // what tenant to select or whether to present all the tenants to select from.
    displayMode: 'optionFirst', // Or identifierFirst
    // The terms of service URL and privacy policy URL for the page
    // where the user select tenant or enter email for tenant/provider
    // matching.
    tosUrl: 'http://localhost/tos',
    privacyPolicyUrl: 'http://localhost/privacypolicy',
    callbacks: {
      // The callback to trigger when the selection tenant page
      // or enter email for tenant matching page is shown.
      selectTenantUiShown: () => {
        // Show title and additional display info.
      },
      // The callback to trigger when the sign-in page
      // is shown.
      signInUiShown: (tenantId) => {
        // Show tenant title and additional display info.
      },
      beforeSignInSuccess: (user) => {
        // Do additional processing on user before sign-in is
        // complete.
        return Promise.resolve(user);
      }
    },
    tenants: {
      // Tenant configuration for tenant ID tenantId1.
      tenantId1: {
        // Full label, display name, button color and icon URL of the
        // tenant selection button. Only needed if you are
        // using the option first option.
        fullLabel: 'ACME Portal',
        displayName: 'ACME',
        buttonColor: '#2F2F2F',
        iconUrl: '<icon-url-of-sign-in-button>',
         // Sign-in providers enabled for tenantId1.
        signInOptions: [
          // Microsoft sign-in.
          {
            provider: 'microsoft.com',
            providerName: 'Microsoft',
            buttonColor: '#2F2F2F',
            iconUrl: '<icon-url-of-sign-in-button>',
            loginHintKey: 'login_hint'
          },
          // Email/password sign-in.
          {
            provider: 'password',
            // Do not require display name on sign up.
            requireDisplayName: false,
            disableSignUp: {
              // Disable user from signing up with email providers.
              status: true,
              adminEmail: 'admin@example.com',
              helpLink: 'https://www.example.com/trouble_signing_in'
            }
          },
          // SAML provider. (multiple SAML providers can be passed)
          {
            provider: 'saml.my-provider1',
            providerName: 'SAML provider',
            fullLabel: 'Employee Login',
            buttonColor: '#4666FF',
            iconUrl: 'https://www.example.com/photos/my_idp/saml.png'
          },
        ],
        // If there is only one sign-in provider eligible for the user,
        // whether to show the provider selection page.
        immediateFederatedRedirect: true,
        signInFlow: 'redirect', // Or popup
        // The terms of service URL and privacy policy URL for the sign-in page
        // specific to each tenant.
        tosUrl: 'http://localhost/tenant1/tos',
        privacyPolicyUrl: 'http://localhost/tenant1/privacypolicy'
      },
      // Tenant configuration for tenant ID tenantId2.
      tenantId2: {
        fullLabel: 'OCP Portal',
        displayName: 'OCP',
        buttonColor: '#2F2F2F',
        iconUrl: '<icon-url-of-sign-in-button>',
        // Tenant2 supports a SAML, OIDC and Email/password sign-in.
        signInOptions: [
          // Email/password sign-in.
          {
            provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
            // Do not require display name on sign up.
            requireDisplayName: false
          },
          // SAML provider. (multiple SAML providers can be passed)
          {
            provider: 'saml.my-provider2',
            providerName: 'SAML provider',
            fullLabel: 'Contractor Portal',
            buttonColor: '#4666FF',
            iconUrl: 'https://www.example.com/photos/my_idp/saml.png'
          },
          // OIDC provider. (multiple OIDC providers can be passed)
          {
            provider: 'oidc.my-provider1',
            providerName: 'OIDC provider',
            buttonColor: '#4666FF',
            iconUrl: 'https://www.example.com/photos/my_idp/oidc.png'
          },
        ],
      },
      // Tenant configuration for tenant ID tenantId3.
      tenantId3: {
        fullLabel: 'Tenant3 Portal',
        displayName: 'Tenant3',
        buttonColor: '#007bff',
        iconUrl: '<icon-url-of-sign-in-button>',
        // Tenant3 supports a Google and Email/password sign-in.
        signInOptions: [
          // Email/password sign-in.
          {
            provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
            // Do not require display name on sign up.
            requireDisplayName: false
          },
          // Google provider.
          {
            provider: 'google.com',
            scopes: ['scope1', 'scope2', 'https://example.com/scope3'],
            loginHintKey: 'login_hint',
            customParameters: {
              prompt: 'consent',
            },
          },
        ],
        // Sets the adminRestrictedOperation configuration for providers
        // including federated, email/password, email link and phone number.
        adminRestrictedOperation: {
          status: true,
          adminEmail: 'admin@example.com',
          helpLink: 'https://www.example.com/trouble_signing_in'
        }
      },
    },
  },
};

Bagian berikut memberikan panduan tentang cara mengonfigurasi beberapa kolom khusus untuk IAP. Untuk contoh cara menetapkan kolom lain, lihat cuplikan kode di atas, atau dokumentasi FirebaseUI di GitHub.

Menetapkan kunci API

Konfigurasi standar dimulai dengan kunci API untuk project Anda:

// The project configuration.
const configs = {
  // Configuration for API_KEY.
  API_KEY: {
    // Config goes here
  }
}

Dalam sebagian besar kasus, Anda hanya perlu menentukan satu kunci API. Namun, jika ingin menggunakan satu URL autentikasi di beberapa project, Anda dapat menyertakan beberapa kunci API:

const configs = {
  API_KEY1: {
    // Config goes here
  },
  API_KEY2: {
    // Config goes here
  },
}

Mendapatkan domain autentikasi

Tetapkan kolom authdomain ke domain yang disediakan untuk memfasilitasi login gabungan. Anda dapat mengambil kolom ini dari halaman Identity Platform di konsol Google Cloud.

Menentukan ID tenant

Konfigurasi memerlukan daftar tenant dan penyedia yang dapat digunakan pengguna untuk melakukan autentikasi.

Setiap tenant diidentifikasi dengan ID-nya. Jika Anda menggunakan autentikasi tingkat project (tanpa tenant), gunakan ID _ khusus sebagai kunci API. Contoh:

const configs = {
  // Configuration for project identified by API key API_KEY1.
  API_KEY1: {
    tenants: {
      // Project-level IdPs flow.
      _: {
        // Tenant config goes here
      },
      // Single tenant flow.
      1036546636501: {
        // Tenant config goes here
      }
    }
  }
}

Anda juga dapat menentukan konfigurasi tenant karakter pengganti menggunakan operator *. Tenant ini digunakan sebagai penggantian jika tidak ada ID yang cocok yang ditemukan.

Mengonfigurasi penyedia tenant

Setiap tenant memiliki penyedianya sendiri; penyedia ini ditentukan di kolom signInOptions:

tenantId1: {
  signInOptions: [
    // Options go here
  ]
}

Lihat Mengonfigurasi penyedia login dalam dokumentasi FirebaseUI untuk mempelajari cara mengonfigurasi penyedia.

Selain langkah-langkah yang diuraikan dalam dokumentasi FirebaseUI, ada beberapa kolom khusus untuk IAP yang bergantung pada mode pemilihan tenant yang Anda pilih. Lihat bagian berikutnya untuk mengetahui informasi selengkapnya tentang kolom ini.

Memilih mode pemilihan tenant

Pengguna dapat memilih tenant dengan dua cara: mode opsi-pertama, atau mode ID-pertama.

Dalam mode opsi, pengguna memulai dengan memilih tenant dari daftar, lalu memasukkan nama pengguna dan sandinya. Dalam mode ID, pengguna memasukkan emailnya terlebih dahulu. Kemudian, sistem akan otomatis memilih tenant pertama dengan penyedia identitas yang cocok dengan domain email.

Untuk menggunakan mode opsi, tetapkan displayMode ke optionFirst. Kemudian, Anda harus memberikan informasi konfigurasi untuk setiap tombol tenant, termasuk displayName, buttonColor, dan iconUrl. fullLabel opsional juga dapat disediakan untuk mengganti seluruh label tombol, bukan hanya nama tampilan.

Berikut adalah contoh tenant yang dikonfigurasi untuk menggunakan mode opsi:

tenantId1: {
  fullLabel: 'ACME Portal',
  displayName: 'ACME',
  buttonColor: '#2F2F2F',
  iconUrl: '<icon-url-of-sign-in-button>',
  // ...

Untuk menggunakan mode ID, setiap opsi login harus menentukan kolom hd yang menunjukkan domain yang didukungnya. Ini dapat berupa ekspresi reguler (seperti /@example\.com$/) atau string domain (mis., example.com).

Kode di bawah menunjukkan tenant yang dikonfigurasi untuk menggunakan mode ID:

tenantId1: {
  signInOptions: [
    // Email/password sign-in.
    {
      hd: 'acme.com', // using regex: /@acme\.com$/
      // ...
    },

Mengaktifkan pengalihan langsung

Jika aplikasi Anda hanya mendukung satu penyedia identitas, menetapkan immediateFederatedRedirect ke true akan melewati UI login dan mengarahkan pengguna langsung ke penyedia.

Menyiapkan callback

Objek konfigurasi berisi kumpulan callback yang dipanggil di berbagai titik selama alur autentikasi. Hal ini juga memungkinkan Anda menyesuaikan UI. Hook berikut tersedia:

selectTenantUiShown() Dipicu saat UI untuk memilih tenant ditampilkan. Gunakan ini jika Anda ingin mengubah UI dengan judul atau tema yang disesuaikan.
signInUiShown(tenantId) Dipicu saat tenant dipilih dan UI untuk pengguna memasukkan kredensialnya ditampilkan. Gunakan ini jika Anda ingin mengubah UI dengan judul atau tema yang disesuaikan.
beforeSignInSuccess(user) Dipicu sebelum login selesai. Gunakan ini untuk mengubah pengguna yang login sebelum mengalihkan kembali ke resource IAP.

Contoh kode berikut menunjukkan cara menerapkan callback ini:

callbacks: {
  selectTenantUiShown: () => {
    // Show info of the IAP resource.
    showUiTitle(
        'Select your employer to access your Health Benefits');
  },
  signInUiShown: (tenantId) => {
    // Show tenant title and additional display info.
    const tenantName = getTenantNameFromId(tenantId);
    showUiTitle(`Sign in to access your ${tenantName} Health Benefits`);
  },
  beforeSignInSuccess: (user) => {
    // Do additional processing on user before sign-in is
    // complete.
    // For example update the user profile.
    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;
    });
  }
}

Melakukan inisialisasi library

Setelah membuat objek konfigurasi, ikuti langkah-langkah berikut untuk melakukan inisialisasi library di halaman autentikasi:

  1. Buat penampung HTML untuk merender UI.

    <!DOCTYPE html>
    <html>
     <head>...</head>
     <body>
       <!-- The surrounding HTML is left untouched by FirebaseUI.
            Your app may use that space for branding, controls and other
            customizations.-->
       <h1>Welcome to My Awesome App</h1>
       <div id="firebaseui-auth-container"></div>
     </body>
    </html>
    
  2. Buat instance FirebaseUiHandler untuk dirender dalam penampung HTML, dan teruskan elemen config yang Anda buat ke instance tersebut.

    const configs = {
      // ...
    }
    const handler = new firebaseui.auth.FirebaseUiHandler(
      '#firebaseui-auth-container', configs);
    
  3. Buat instance Authentication baru, teruskan pengendali ke instance tersebut, lalu panggil start().

    const ciapInstance = new ciap.Authentication(handler);
    ciapInstance.start();
    

Deploy aplikasi Anda dan buka halaman autentikasi. UI login yang berisi tenant dan penyedia Anda akan muncul.

Langkah selanjutnya