本文將說明如何使用外部身分和 IAP 建構自己的驗證頁面。自行建構這個頁面可讓您全面掌控驗證流程和使用者體驗。
如果不需要全面自訂 UI,可以讓 IAP 代管登入頁面,或使用 FirebaseUI 獲得更簡化的體驗。
總覽
如要自行建構驗證頁面,請按照下列步驟操作:
- 啟用外部身分。在設定期間選取「I'll provide my own UI option」(我會自行提供 UI 選項)。
- 安裝
gcip-iap程式庫。 - 實作
AuthenticationHandler介面來設定 UI。驗證頁面必須處理下列情況:- 選取租戶
- 使用者授權
- 使用者登入
- 處理錯誤
- 選用:使用其他功能自訂驗證頁面,例如進度列、登出頁面和使用者處理程序。
- 測試 UI。
安裝 gcip-iap 程式庫
如要安裝 gcip-iap 程式庫,請執行下列指令:
npm install gcip-iap --save
gcip-iap NPM 模組會抽象化應用程式、IAP 和 Identity Platform 之間的通訊。這樣一來,您就能自訂整個驗證流程,不必管理 UI 和 IAP 之間的基礎交易。
請使用適用於您 SDK 版本的正確匯入項目:
gcip-iap v0.1.4 或更早版本
// 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 至 v1.1.0
自 v1.0.0 版起,gcip-iap 需要 firebase v9 以上的對等依附元件。如要遷移至 gcip-iap v1.0.0 以上版本,請完成下列動作:
- 將
package.json檔案中的firebase版本更新為 9.6.0 以上。 - 將
firebase匯入陳述式更新為下列內容:
// Import Firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import the gcip-iap module.
import * as ciap from 'gcip-iap';
不需要變更其他程式碼。
gcip-iap v2.0.0
從 v2.0.0 版開始,gcip-iap 需要使用模組化 SDK 格式,重新編寫自訂 UI 應用程式。如要遷移至 gcip-iap v2.0.0 以上版本,請完成下列動作:
- 將
package.json檔案中的firebase版本更新為 v9.8.3 以上。 - 將
firebase匯入陳述式更新為下列內容:
// Import Firebase modules.
import { initializeApp } from 'firebase/app';
import { getAuth, GoogleAuthProvider } 'firebase/auth';
// Import the gcip-iap module.
import * as ciap from 'gcip-iap';
設定 UI
如要設定 UI,請建立實作 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;
}
驗證期間,程式庫會自動呼叫 AuthenticationHandler 的方法。
選取租戶
如要選取租戶,請實作 selectTenant()。您可以實作這個方法,以程式輔助方式選擇租戶,或顯示 UI 讓使用者自行選取。
無論是哪一種情況,程式庫都會使用傳回的 SelectedTenantInfo 物件完成驗證流程。其中包含所選租戶的 ID、所有 provider ID,以及使用者輸入的電子郵件地址。
如果專案有多個房客,您必須先選取一個,才能驗證使用者。如果您只有單一租戶,或使用專案層級驗證,則不需要實作 selectTenant()。
IAP 支援與 Identity Platform 相同的供應商,例如:
- 電子郵件地址和密碼
- OAuth (Google、Facebook、Twitter、GitHub、Microsoft 等)
- SAML
- OIDC
- 電話號碼
- 自訂
- 匿名
多重租戶不支援電話號碼、自訂和匿名驗證類型。
以程式輔助方式選取用戶群
如要以程式輔助方式選取租戶,請善用目前的環境。Authentication 類別包含 getOriginalURL(),該類別會傳回使用者在驗證前存取的網址。
使用這項功能,從相關聯的租戶清單中找出相符項目:
// 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);
});
}
允許使用者選取房客
如要允許使用者選取租戶,請顯示租戶清單並讓使用者選擇其中一個,或要求使用者輸入電子郵件地址,然後根據網域尋找相符項目:
// 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,
});
});
});
}
驗證使用者
取得供應商後,請實作 getAuth(),傳回與提供的 API 金鑰和租戶 ID 相對應的 Auth 執行個體。如未提供租戶 ID,請使用專案層級的識別資訊提供者。
getAuth() 會追蹤與所提供設定對應的使用者儲存位置。此外,這項功能還可讓先前通過驗證的使用者,在不必重新輸入憑證的情況下,無須互動即可重新整理 Identity Platform ID 權杖。
如果您使用多個 IAP 資源,且這些資源屬於不同租戶,建議您為每個資源使用不重複的驗證執行個體。這樣一來,多個設定不同的資源就能使用相同的驗證頁面。此外,多位使用者可以同時登入,不必登出先前的使用者。
以下是 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;
}
登入使用者
如要處理登入作業,請實作 startSignIn(),顯示使用者驗證的 UI,然後在完成時傳回已登入使用者的 UserCredential。
在多租戶環境中,如果提供 SelectedTenantInfo,您可以從中判斷可用的驗證方法。這個變數包含 selectTenant() 傳回的相同資訊。
以下範例說明如何為現有使用者 (透過電子郵件地址和密碼) 實作 startSignIn():
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.
});
});
});
}
您也可以使用彈出式視窗或重新導向,透過 SAML 或 OIDC 等聯合驗證供應商登入使用者:
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);
});
}
部分 OAuth 供應商支援傳遞登入提示以進行登入:
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.
});
});
}
詳情請參閱「使用多重租戶進行驗證」。
處理錯誤
如要向使用者顯示錯誤訊息,或嘗試從網路逾時等錯誤中復原,請實作 handleError()。
以下範例實作 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;
});
}
下表列出可能傳回的 IAP 專屬錯誤代碼。Identity Platform 也可能會傳回錯誤,請參閱firebase.auth.Auth說明文件。
| 錯誤代碼 | 說明 |
|---|---|
invalid-argument |
用戶端指定的引數無效。 |
failed-precondition |
無法在目前的系統狀態下執行要求。 |
out-of-range |
用戶端指定的範圍無效。 |
unauthenticated |
OAuth 權杖遺漏、無效或過期,因此無法驗證要求。 |
permission-denied |
用戶端權限不足,或 UI 託管於未經授權的網域。 |
not-found。 |
找不到您指定的資源。 |
aborted |
發生並行衝突,例如讀取-修改-寫入衝突。 |
already-exists |
用戶端嘗試建立的資源已存在。 |
resource-exhausted |
資源配額用盡或達到頻率限制。 |
cancelled |
用戶端已取消要求。 |
data-loss |
發生無法復原的資料遺失或資料毀損情形。 |
unknown |
發生不明的伺服器錯誤。 |
internal |
內部伺服器錯誤。 |
not-implemented |
伺服器未執行 API 方法。 |
unavailable |
無法使用服務。 |
restart-process |
請再次點選當初將您重新導向至這個頁面的網址,以便重新啟動驗證程序。 |
deadline-exceeded |
已超出要求期限。 |
authentication-uri-fail |
無法產生驗證 URI。 |
gcip-token-invalid |
提供的 GCIP ID 權杖無效。 |
gcip-redirect-invalid |
重新導向網址無效。 |
get-project-mapping-fail |
無法取得專案 ID。 |
gcip-id-token-encryption-error |
Google Cloud Identity Platform ID 權杖加密錯誤。 |
gcip-id-token-decryption-error |
Google Cloud Identity Platform ID 權杖解密錯誤。 |
gcip-id-token-unescape-error |
無法解除網路安全 Base64 編碼。 |
resource-missing-gcip-sign-in-url |
指定 IAP 資源缺少 GCIP 驗證網址。 |
自訂 UI
您可以自訂驗證頁面,並加入進度列和登出頁面等選用功能。
顯示進度 UI
每當 gcip-iap 模組執行長時間執行的網路工作時,如要向使用者顯示自訂進度使用者介面,請實作 showProgressBar() 和 hideProgressBar()。
將使用者登出
在某些情況下,您可能會想允許使用者登出所有共用相同驗證網址的目前工作階段。
使用者登出後,可能沒有網址可將他們重新導向回原網頁。
如果使用者從與登入頁面相關聯的所有租戶登出,通常就會發生這種情況。在這種情況下,請實作 completeSignOut(),顯示使用者已成功登出的訊息。如果您未實作這個方法,使用者登出時會看到空白頁面。
正在處理使用者
如要在重新導向至 IAP 資源前修改登入的使用者,請實作 processUser()。
您可以使用這個方法執行下列操作:
- 連結至其他供應商。
- 更新使用者的個人資料。
- 在註冊後要求使用者提供額外資料。
- 處理
signInWithRedirect()呼叫後,getRedirectResult()傳回的 OAuth 存取權杖。
以下是實作 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;
});
}
如要讓 IAP 傳播至應用程式的 ID 權杖聲明反映使用者的任何變更,請務必強制重新整理權杖:
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;
});
}
測試 UI
建立實作 AuthenticationHandler 的類別後,您可以使用該類別建立新的 Authentication 例項並啟動:
// Implement interface AuthenticationHandler.
// const authHandlerImplementation = ....
const ciapInstance = new ciap.Authentication(authHandlerImplementation);
ciapInstance.start();
部署應用程式,然後前往驗證頁面。您應該會看到自訂登入 UI。
後續步驟
- 瞭解如何透過程式存取非 Google 資源。
- 瞭解如何管理工作階段。
- 瞭解外部身分識別如何搭配 IAP 運作。