כדי להשתמש בזהויות חיצוניות עם שרת Proxy לאימות זהויות (IAP), צריך דף כניסה לאפליקציה. שרת IAP יפנה את המשתמשים לדף הזה כדי לבצע אימות לפני שהם יוכלו לגשת למשאבים מאובטחים.
במאמר הזה נסביר איך ליצור דף אימות באמצעות FirebaseUI, ספריית JavaScript בקוד פתוח. ב-FirebaseUI יש רכיבים שאפשר להתאים אישית, שעוזרים לצמצם את הקוד שחוזר על עצמו (boilerplate) ומטפלים בתהליכים של כניסת משתמשים עם מגוון רחב של ספקי זהויות.
כדי להתחיל מהר יותר, אפשר לתת ל-IAP לארח את ממשק המשתמש בשבילכם. כך תוכלו לנסות זהויות חיצוניות בלי לכתוב קוד נוסף. בתרחישים מתקדמים יותר, אפשר גם ליצור דף כניסה משלכם מאפס. האפשרות הזו מורכבת יותר, אבל היא נותנת לכם שליטה מלאה על תהליך האימות ועל חוויית המשתמש.
לפני שמתחילים
מפעילים זהויות חיצוניות ובוחרים באפשרות I'll provide my own UI במהלך ההגדרה.
התקנת הספריות
מתקינים את הספריות gcip-iap, firebase ו-firebaseui. המודול gcip-iap מסכם את התקשורת בין האפליקציה, IAP ו-Identity Platform. הספריות firebase ו-firebaseui מספקות את אבני הבניין לממשק המשתמש של האימות.
npm install firebase --save
npm install firebaseui --save
npm install gcip-iap --save
שימו לב שהמודול gcip-iap לא זמין באמצעות CDN.
אחרי זה, אפשר import את המודולים בקובצי המקור. משתמשים בייבוא הנכון לגרסת ה-SDK:
gcip-iap בגרסה 0.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 ואילך
החל מגרסה v1.0.0, gcip-iap דורש את יחסי התלות של firebase מגרסה v9 ומעלה.
אם אתם עוברים לגרסה 1.0.0 ומעלה של gcip-iap, בצעו את הפעולות הבאות:
מעדכנים את הגרסאות של
firebaseו-firebaseuiבקובץpackage.jsonלגרסה 9.6.0 ואילך ולגרסה 6.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. דוגמאות להגדרת שדות אחרים מופיעות בקטע הקוד שלמעלה או במסמכי התיעוד של FirebaseUI ב-GitHub.
הגדרת מפתח ה-API
הגדרה אופיינית מתחילה במפתח API לפרויקט:
// The project configuration.
const configs = {
// Configuration for API_KEY.
API_KEY: {
// Config goes here
}
}
ברוב המקרים צריך לציין רק מפתח API אחד. עם זאת, אם רוצים להשתמש בכתובת URL אחת לאימות בכמה פרויקטים, אפשר לכלול כמה מפתחות API:
const configs = {
API_KEY1: {
// Config goes here
},
API_KEY2: {
// Config goes here
},
}
קבלת דומיין האימות
מגדירים את השדה authdomain לדומיין שהוקצה כדי לאפשר כניסה מאוחדת. אפשר לאחזר את השדה הזה מהדף Identity Platform במסוף Google Cloud .
ציון מזהי דיירים
כדי להגדיר את המדיניות, צריך רשימה של דיירים וספקים שהמשתמשים יכולים לאמת את הזהות שלהם דרכם.
כל דייר מזוהה באמצעות המזהה שלו. אם אתם משתמשים באימות ברמת הפרויקט (ללא דיירים), השתמשו במזהה המיוחד _ כמפתח 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
}
}
}
}
אפשר גם לציין הגדרת דייר עם תו כללי לחיפוש באמצעות האופרטור *.
הדייר הזה משמש כברירת מחדל אם לא נמצא מזהה תואם.
הגדרת ספקי דיירים (tenant)
לכל דייר יש ספקים משלו, שמצוינים בשדה signInOptions:
tenantId1: {
signInOptions: [
// Options go here
]
}
במאמר הגדרת ספקי כניסה במסמכי התיעוד של FirebaseUI מוסבר איך להגדיר ספקים.
בנוסף לשלבים שמפורטים במסמכי התיעוד של FirebaseUI, יש כמה שדות ספציפיים ל-IAP שתלויים במצב בחירת הדייר שבחרתם. בקטע הבא מוסבר בהרחבה על השדות האלה.
בחירת מצב לבחירת דייר (tenant)
המשתמשים יכולים לבחור דייר בשתי דרכים: מצב "אפשרויות תחילה" או מצב "מזהה תחילה".
במצב אפשרויות, המשתמש מתחיל בבחירת דייר מרשימה, ואז מזין את שם המשתמש והסיסמה שלו. במצב מזהה, המשתמש מזין קודם את כתובת האימייל שלו. לאחר מכן, המערכת בוחרת באופן אוטומטי את הדייר הראשון עם ספק זהויות שתואם לדומיין של כתובת האימייל.
כדי להשתמש במצב אפשרויות, מגדירים את displayMode לערך optionFirst. לאחר מכן תצטרכו לספק את פרטי ההגדרה של הלחצן של כל דייר, כולל displayName, buttonColor ו-iconUrl. אפשר גם להוסיף את השדה האופציונלי fullLabel כדי לשנות את כל התווית של הלחצן, ולא רק את השם המוצג.
הדוגמה הבאה מראה איך להגדיר דייר לשימוש במצב אפשרויות:
tenantId1: {
fullLabel: 'ACME Portal',
displayName: 'ACME',
buttonColor: '#2F2F2F',
iconUrl: '<icon-url-of-sign-in-button>',
// ...
כדי להשתמש במצב מזהה, כל אפשרות כניסה צריכה לציין שדה hd שמציין באיזה דומיין היא תומכת. הערך יכול להיות ביטוי רגולרי (כמו /@example\.com$/) או מחרוזת הדומיין (למשל, example.com).
הקוד הבא מציג דייר שמוגדר להשתמש במצב מזהה:
tenantId1: {
signInOptions: [
// Email/password sign-in.
{
hd: 'acme.com', // using regex: /@acme\.com$/
// ...
},
הפעלת הפניה אוטומטית מיידית
אם האפליקציה תומכת רק בספק זהויות אחד, הגדרת immediateFederatedRedirect ל-true תדלג על ממשק המשתמש של הכניסה ותפנה את המשתמש ישירות לספק.
הגדרת קריאות חוזרות (callback)
אובייקט ההגדרות מכיל קבוצה של פונקציות קריאה חוזרת (callback) שמופעלות בנקודות שונות במהלך תהליך האימות. כך תוכלו להתאים אישית את ממשק המשתמש. אלה ה-hooks שזמינים:
selectTenantUiShown() |
מופעל כשמוצג ממשק המשתמש לבחירת דייר. אפשר להשתמש בזה אם רוצים לשנות את ממשק המשתמש באמצעות כותרת או עיצוב בהתאמה אישית. |
signInUiShown(tenantId) |
מופעל כשנבחר דייר ומוצג ממשק המשתמש שבו המשתמש יכול להזין את פרטי הכניסה שלו. אפשר להשתמש באפשרות הזו כדי לשנות את ממשק המשתמש באמצעות כותרת או עיצוב בהתאמה אישית. |
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 כדי לעבד את ממשק המשתמש.
<!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, מעבירים אליו את ה-handler וקוראים ל-start().const ciapInstance = new ciap.Authentication(handler); ciapInstance.start();
פורסים את האפליקציה ועוברים לדף האימות. יופיע ממשק משתמש לכניסה שמכיל את הדיירים והספקים שלכם.