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 yang aman.

Artikel ini menunjukkan cara membuat halaman autentikasi menggunakan FirebaseUI, library JavaScript open source. FirebaseUI menyediakan elemen yang dapat disesuaikan untuk 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 lainnya, Anda juga dapat membuat halaman login 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 memberikan UI sendiri selama penyiapan.

Menginstal library

Instal library gcip-iap, firebase, dan firebaseui. Modul gcip-iap mengabstraksi 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 melakukan import pada modul di 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 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 dan firebaseui di 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 lainnya, termasuk menggunakan versi library yang dilokalkan, lihat petunjuk di GitHub.

Mengonfigurasi aplikasi Anda

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 cara mengonfigurasi beberapa kolom khusus untuk IAP. Untuk mengetahui contoh cara menetapkan kolom lain, lihat cuplikan kode di atas atau dokumentasi FirebaseUI di GitHub.

Menetapkan kunci API

Konfigurasi umum dimulai dengan kunci API untuk project Anda:

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

Pada umumnya, 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 Google Cloud Console.

Menentukan ID tenant

Konfigurasi memerlukan daftar tenant dan penyedia yang dapat diautentikasi oleh pengguna.

Setiap tenant diidentifikasi berdasarkan ID-nya. Jika Anda menggunakan autentikasi level 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 ditemukan ID yang cocok.

Mengonfigurasi penyedia tenant

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

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

Lihat bagian 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 pertama opsi, atau mode pertama ID.

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

Untuk menggunakan mode opsi, setel displayMode ke optionFirst. Kemudian, Anda harus memberikan informasi konfigurasi untuk setiap tombol tenant, termasuk displayName, buttonColor, dan iconUrl. fullLabel opsional juga dapat diberikan 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. String ini dapat berupa ekspresi reguler (seperti /@example\.com$/) atau string domain (misalnya, 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 mengalihkan pengguna langsung ke penyedia.

Menyiapkan callback

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

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

Kode contoh 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 Anda membuat objek konfigurasi, ikuti langkah-langkah berikut untuk menginisialisasi library di halaman autentikasi:

  1. Buat container 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 di penampung HTML, lalu 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