במאמר הזה מוסבר איך ליצור דף אימות משלכם באמצעות זהויות חיצוניות ו-IAP. אם אתם יוצרים את הדף הזה בעצמכם, אתם מקבלים שליטה מלאה בתהליך האימות ובחוויית המשתמש.
אם אתם לא צריכים להתאים אישית את ממשק המשתמש באופן מלא, אתם יכולים לאפשר ל-IAP לארח בשבילכם דף כניסה, או להשתמש ב-FirebaseUI כדי ליהנות מחוויה יעילה יותר.
סקירה כללית
כדי ליצור דף אימות משלכם, פועלים לפי השלבים הבאים:
- הפעלת זהויות חיצוניות במהלך ההגדרה, בוחרים באפשרות I'll provide my own UI option (אני אציין אפשרות משלי לממשק המשתמש).
- מתקינים את הספרייה
gcip-iap. - מגדירים את ממשק המשתמש באמצעות הטמעה של הממשק
AuthenticationHandler. דף האימות צריך לטפל בתרחישים הבאים:- בחירת דייר
- הרשאת משתמש
- כניסה של משתמשים
- טיפול בשגיאות
- אופציונלי: התאמה אישית של דף האימות עם תכונות נוספות, כמו סרגלי התקדמות, דפי יציאה ועיבוד משתמשים.
- בדיקת ממשק המשתמש.
התקנה של ספריית gcip-iap
כדי להתקין את הספרייה gcip-iap, מריצים את הפקודה הבאה:
npm install gcip-iap --save
מודול ה-gcip-iap NPM מבצע הפשטה של התקשורת בין האפליקציה, IAP ו-Identity Platform. כך אפשר להתאים אישית את כל תהליך האימות בלי לנהל את חילופי הנתונים הבסיסיים בין ממשק המשתמש לבין 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 to v1.1.0
החל מגרסה v1.0.0, gcip-iap דורש תלות בעמיתים בגרסה firebase v9 ומעלה.
אם אתם עוברים לגרסה gcip-iap v1.0.0 ומעלה, אתם צריכים לבצע את הפעולות הבאות:
- מעדכנים את גרסת
firebaseבקובץpackage.jsonלגרסה v9.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 המודולרי. אם אתם מעבירים לגרסה gcip-iap v2.0.0 ומעלה, צריך לבצע את הפעולות הבאות:
- מעדכנים את גרסת
firebaseבקובץpackage.jsonלגרסה 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';
הגדרת ממשק המשתמש
כדי להגדיר את ממשק המשתמש, יוצרים מחלקה בהתאמה אישית שמטמיעה את הממשק 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.
בחירת דיירים (tenants)
כדי לבחור דייר, מטמיעים את selectTenant(). אפשר להטמיע את השיטה הזו כדי לבחור דייר באופן פרוגרמטי, או להציג ממשק משתמש כדי שהמשתמש יוכל לבחור דייר בעצמו.
בכל מקרה, הספרייה משתמשת באובייקט SelectedTenantInfo שמוחזר כדי להשלים את תהליך האימות. הוא מכיל את המזהה של הדייר שנבחר, את המזהים של הספקים וכתובת האימייל שהמשתמש הזין.
אם יש לכם כמה דיירים בפרויקט, אתם צריכים לבחור אחד מהם לפני שתוכלו לאמת משתמש. אם יש לכם רק דייר אחד, או אם אתם משתמשים באימות ברמת הפרויקט, אתם לא צריכים להטמיע את selectTenant().
IAP תומך באותם ספקים כמו Identity Platform, למשל:
- כתובת אימייל וסיסמה
- OAuth (Google, Facebook, Twitter, GitHub, Microsoft וכו')
- SAML
- OIDC
- מספר טלפון
- בהתאמה אישית
- אנונימי
אין תמיכה בסוגי אימות של מספר טלפון, מותאם אישית ואנונימיות במערכות מרובות דיירים.
בחירת דיירים באופן פרוגרמטי
כדי לבחור דייר באופן פרוגרמטי, צריך להשתמש בהקשר הנוכחי. המחלקות Authentication מכילות את getOriginalURL() שמחזירה את כתובת ה-URL שהמשתמש ניסה לגשת אליה לפני האימות.
כדי לאתר התאמה מרשימה של דיירים משויכים:
// 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() כדי להחזיר מופע של Auth, שמתאים למפתח ה-API ולמזהה הדייר שסופקו. אם לא מצוין מזהה דייר, משתמשים בספקי זהויות ברמת הפרויקט.
getAuth() עוקב אחרי המקום שבו מאוחסן המשתמש שתואם להגדרה שצוינה. בנוסף, אפשר לרענן בשקט את אסימון ה-ID של משתמש שאומת בעבר ב-Identity Platform, בלי שהמשתמש יצטרך להזין מחדש את פרטי הכניסה שלו.
אם אתם משתמשים בכמה משאבי 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(), להציג ממשק משתמש לאימות המשתמש ואז להחזיר 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 |
ללקוח אין הרשאה מספיקה, או שממשק המשתמש מתארח בדומיין לא מורשה. |
not-found . |
המשאב שצוין לא נמצא. |
aborted |
התנגשות מסוג בו-זמניות (Concurrency), כגון התנגשות בפעולה read-modify-write (קריאה-שינוי-כתיבה). |
already-exists |
המשאב שהלקוח ניסה ליצור כבר קיים. |
resource-exhausted |
מכסת המשאבים מוצתה או שהגבלת הקצב של יצירת הבקשות תמוצה בקרוב. |
cancelled |
הבקשה בוטלה על ידי הלקוח. |
data-loss |
פגם בנתונים או אובדן נתונים שלא ניתן לשחזר. |
unknown |
שגיאת שרת לא ידועה. |
internal |
שגיאת שרת פנימית. |
not-implemented |
שיטת ה-API לא הוטמעה על ידי השרת. |
unavailable |
השירות לא זמין. |
restart-process |
כדי להתחיל מחדש את תהליך האימות, צריך להיכנס שוב לכתובת ה-URL שהפנתה אותך לדף הזה. |
deadline-exceeded |
המועד האחרון לשליחת הבקשה חלף. |
authentication-uri-fail |
יצירת URI לאימות נכשלה. |
gcip-token-invalid |
סופק טוקן GCIP ID לא תקין. |
gcip-redirect-invalid |
כתובת ה-URL של ההפניה האוטומטית לא תקינה. |
get-project-mapping-fail |
שליפת מזהה הפרויקט נכשלה. |
gcip-id-token-encryption-error |
שגיאה בהצפנת טוקן המזהה של GCIP. |
gcip-id-token-decryption-error |
שגיאה בפענוח טוקן המזהה של GCIP. |
gcip-id-token-unescape-error |
ביטול האסקייפ של base64 בטוח לאינטרנט נכשל. |
resource-missing-gcip-sign-in-url |
חסרה כתובת URL לאימות GCIP עבור משאב ה-IAP שצוין. |
התאמה אישית של ממשק המשתמש
אתם יכולים להתאים אישית את דף האימות באמצעות תכונות אופציונליות כמו סרגלי התקדמות ודפי יציאה מהחשבון.
הצגת ממשק משתמש של התקדמות
כדי להציג למשתמש ממשק משתמש מותאם אישית להתקדמות בכל פעם שמודול gcip-iap מבצע משימות רשת ארוכות, צריך להטמיע את showProgressBar() ואת hideProgressBar().
הוצאת משתמשים מהחשבון
במקרים מסוימים, יכול להיות שתרצו לאפשר למשתמשים להתנתק מכל הסשנים הנוכחיים שמשתפים את אותה כתובת URL לאימות.
אחרי שמשתמש יוצא מהחשבון, יכול להיות שלא תהיה כתובת URL להפניה מחדש.
זה קורה בדרך כלל כשמשתמש יוצא מהחשבון בכל הדיירים שמשויכים לדף הכניסה. במקרה כזה, צריך להטמיע את completeSignOut() כדי להציג הודעה שמציינת שהמשתמש התנתק בהצלחה. אם לא מטמיעים את השיטה הזו,
מוצג דף ריק כשמשתמש מתנתק.
עיבוד של נתוני משתמשים
כדי לשנות משתמש מחובר לפני שמפנים אותו למשאב IAP, צריך להטמיע את processUser().
אפשר להשתמש בשיטה הזו כדי:
- קישור לספקים נוספים.
- מעדכנים את הפרופיל של המשתמש.
- בקשת נתונים נוספים מהמשתמש אחרי ההרשמה.
- עיבוד של אסימוני גישה מסוג OAuth שמוחזרים על ידי
getRedirectResult()אחרי קריאה ל-signInWithRedirect().
דוגמה להטמעה של 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;
});
}
אם אתם רוצים ששינויים שבוצעו במשתמש ישתקפו בהצהרות של אסימון המזהה שמועבר מהחיובים מתוך האפליקציה לאפליקציה שלכם, אתם צריכים לאלץ את רענון האסימון:
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;
});
}
בדיקת ממשק המשתמש
אחרי שיוצרים מחלקה שמטמיעה את AuthenticationHandler, אפשר להשתמש בה כדי ליצור מופע חדש של Authentication ולהפעיל אותו:
// Implement interface AuthenticationHandler.
// const authHandlerImplementation = ....
const ciapInstance = new ciap.Authentication(authHandlerImplementation);
ciapInstance.start();
פורסים את האפליקציה ועוברים לדף האימות. אמור להופיע ממשק משתמש מותאם אישית לכניסה.
המאמרים הבאים
- איך ניגשים למשאבים שלא שייכים ל-Google באמצעות תוכנה
- מידע נוסף על ניהול סשנים
- איך זה עובד עם זהויות חיצוניות ב-IAP