如要搭配 Identity-Aware Proxy (IAP) 使用外部身分,應用程式需要登入頁面。IAP 會將使用者重新導向至此頁面,要求他們驗證身分,才能存取安全資源。
本文將說明如何使用 FirebaseUI (開放原始碼 JavaScript 程式庫) 建構驗證頁面。FirebaseUI 提供可自訂的元素,有助於減少樣板程式碼,並處理使用者透過各種身分識別資訊提供者登入的流程。
如要加快開始使用,請讓 IAP 代管 UI。這樣一來,您就能嘗試使用外部身分,而不必編寫任何額外的程式碼。如要處理更進階的情況,您也可以自行從頭開始建構登入頁面。這個選項較為複雜,但可讓您全面控管驗證流程和使用者體驗。
事前準備
啟用外部身分,並在設定期間選取「我會自行提供 UI」選項。
安裝程式庫
請安裝 gcip-iap、firebase 和 firebaseui 程式庫。gcip-iap 模組會擷取應用程式、IAP 和 Identity Platform 之間的通訊。firebase 和 firebaseui 程式庫提供驗證 UI 的建構模塊。
npm install firebase --save
npm install firebaseui --save
npm install gcip-iap --save
請注意,您無法使用 CDN 提供 gcip-iap 模組。
接著,您就可以在來源檔案中 import 模組。請使用 SDK 版本適用的正確匯入方式:
gcip-iap v0.1.4 或更早版本
// 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 1.0.0 以上版本
自 1.0.0 版起,gcip-iap 需要 firebase v9 以上版本的對等依附元件。如果您要遷移至 gcip-iap v1.0.0 以上版本,請完成下列操作:
- 請將 package.json檔案中的firebase和firebaseui版本分別更新為 v9.6.0 以上版本和 v6.0.0 以上版本。
- 更新 firebase匯入陳述式,如下所示:
// 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.
不需要額外變更程式碼。
如需其他安裝選項 (包括使用本地化版本的程式庫),請參閱 GitHub 上的操作說明。
設定應用程式
FirebaseUI 會使用設定物件,指定用於驗證的租用戶和供應者。完整設定可能會非常長,可能會像這樣:
// 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'
        }
      },
    },
  },
};
以下各節將說明如何設定部分特定於 IAP 的欄位。如需其他欄位設定的範例,請參閱上述程式碼片段或 GitHub 上的 FirebaseUI 說明文件。
設定 API 金鑰
一般設定會先從專案的 API 金鑰開始:
// The project configuration.
const configs = {
  // Configuration for API_KEY.
  API_KEY: {
    // Config goes here
  }
}
在大多數情況下,您只需要指定一個 API 金鑰。不過,如果您想在多個專案中使用單一驗證網址,可以加入多個 API 金鑰:
const configs = {
  API_KEY1: {
    // Config goes here
  },
  API_KEY2: {
    // Config goes here
  },
}
取得驗證網域
將 authdomain 欄位設為已佈建的網域,以利聯合登入。您可以從  Google Cloud 控制台的 Identity Platform 頁面擷取這個欄位。
指定租戶 ID
設定需要使用者可用於驗證的租用戶和提供者清單。
每個租用戶都會以 ID 做為識別。如果您使用專案層級驗證 (沒有租用戶),請改用特殊的 _ 識別碼做為 API 金鑰。例如:
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
      }
    }
  }
}
您也可以使用 * 運算子指定萬用字元租用戶設定。如果找不到相符的 ID,系統會使用這個租用戶做為備用方案。
設定租用戶供應器
每個租用戶都有自己的供應者,這些供應者會在 signInOptions 欄位中指定:
tenantId1: {
  signInOptions: [
    // Options go here
  ]
}
如要瞭解如何設定供應商,請參閱 FirebaseUI 說明文件中的「設定登入供應商」。
除了 FirebaseUI 說明文件中概略說明的步驟之外,還有幾個特定於 IAP 的欄位,取決於您選擇的租用戶選取模式。如要進一步瞭解這些欄位,請參閱下一節。
選擇租戶選取模式
使用者可以透過兩種方式選取租用戶:選項優先模式或ID 優先模式。
在選項模式中,使用者會先從清單中選取租用戶,然後輸入使用者名稱和密碼。在 ID 模式中,使用者會先輸入電子郵件地址。系統會自動選取第一個租用戶,其中的 ID 提供者與電子郵件網域相符。
如要使用選項模式,請將 displayMode 設為 optionFirst。接著,您需要為每個租用戶的按鈕提供設定資訊,包括 displayName、buttonColor 和 iconUrl。您也可以提供選用的 fullLabel,用於覆寫整個按鈕標籤,而非只覆寫顯示名稱。
以下是設定為使用選項模式的租用戶範例:
tenantId1: {
  fullLabel: 'ACME Portal',
  displayName: 'ACME',
  buttonColor: '#2F2F2F',
  iconUrl: '<icon-url-of-sign-in-button>',
  // ...
如要使用 ID 模式,每個登入選項都必須指定 hd 欄位,指出所支援的網域。這可以是正規表示式 (例如 /@example\.com$/) 或網域字串 (例如 example.com)。
下方程式碼顯示租用戶已設定為使用 ID 模式:
tenantId1: {
  signInOptions: [
    // Email/password sign-in.
    {
      hd: 'acme.com', // using regex: /@acme\.com$/
      // ...
    },
啟用立即重新導向
如果您的應用程式只支援單一 ID 供應器,將 immediateFederatedRedirect 設為 true 會略過登入 UI,並將使用者直接重新導向至供應器。
設定回呼
設定物件包含一組回呼,會在驗證流程中的不同時間點叫用。這樣一來,您還可以自訂 UI。可用的掛鉤如下:
| selectTenantUiShown() | 在顯示租用戶選取 UI 時觸發。如果您想使用自訂標題或主題修改 UI,請使用這個選項。 | 
| signInUiShown(tenantId) | 在選取租用戶並顯示使用者輸入憑證的 UI 時觸發。如果您想使用自訂標題或主題修改 UI,請使用這個選項。 | 
| beforeSignInSuccess(user) | 在登入完成前觸發。您可以使用這個方法修改已登入的使用者,然後再重新導向至 IAP 資源。 | 
以下程式碼範例說明如何實作這些回呼:
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;
    });
  }
}
初始化程式庫
建立設定物件後,請按照下列步驟在驗證頁面上初始化程式庫:
- 建立 HTML 容器,用於轉譯 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>
- 建立 - FirebaseUiHandler例項,以便在 HTML 容器中算繪,並將您建立的- config元素傳遞給該例項。- const configs = { // ... } const handler = new firebaseui.auth.FirebaseUiHandler( '#firebaseui-auth-container', configs);
- 建立新的 - Authentication例項,將處理常式傳遞至該例項,然後呼叫- start()。- const ciapInstance = new ciap.Authentication(handler); ciapInstance.start();
部署應用程式並前往驗證頁面。系統應會顯示內含租戶和供應者的登入 UI。
後續步驟
- 瞭解如何透過程式輔助的方式存取非 Google 資源。
- 瞭解如何管理工作階段。
- 進一步瞭解外部身分識別資訊與 IAP 的搭配運作方式。