במאמר הזה מוסבר איך ליצור דף אימות משלכם באמצעות זהויות חיצוניות ו-IAP. אם אתם יוצרים את הדף בעצמכם, יש לכם שליטה מלאה על תהליך האימות ועל חוויית המשתמש.
אם אתם לא צריכים להתאים אישית את ממשק המשתמש, אתם יכולים לאפשר ל-IAP לארח דף כניסה בשבילכם, או להשתמש ב-FirebaseUI כדי לייעל את התהליך.
סקירה כללית
כדי ליצור דף אימות משלכם, בצעו את השלבים הבאים:
- הפעלת זהויות חיצוניות. במהלך ההגדרה, בוחרים באפשרות אני אגדיר ממשק משתמש משלי.
- מתקינים את הספרייה
gcip-iap. מגדירים את ממשק המשתמש על ידי הטמעת הממשק
AuthenticationHandler.דף האימות צריך לטפל בתרחישים הבאים:
בחירת דייר
הרשאת משתמשים
כניסת משתמשים לחשבון
טיפול בשגיאות
אופציונלי: התאמה אישית של דף האימות באמצעות תכונות נוספות, כמו סרגלי התקדמות, דפי יציאה ועיבוד משתמשים.
התקנת הספרייה gcip-iap
כדי להתקין את הספרייה gcip-iap, מריצים את הפקודה הבאה:
npm install gcip-iap --save
מודול ה-NPM של gcip-iap מסכם את התקשורת בין האפליקציה, IAP ו-Identity Platform. כך אפשר להתאים אישית את כל תהליך האימות בלי לנהל את חילופי הנתונים הבסיסיים בין ממשק המשתמש ל-IAP.
משתמשים בייבוא הנכון לגרסת ה-SDK:
gcip-iap בגרסה 0.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 מגרסה 1.0.0 עד גרסה 1.1.0
החל מגרסה v1.0.0, gcip-iap דורש את התלות המקבילה ב-firebase בגרסה v9 ואילך.
אם אתם עוברים לגרסה 1.0.0 ומעלה של gcip-iap, בצעו את הפעולות הבאות:
- מעדכנים את גרסת
firebaseבקובץpackage.jsonלגרסה 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
החל מגרסה 2.0.0, כדי להשתמש ב-gcip-iap צריך לשכתב את אפליקציית ממשק המשתמש המותאם אישית באמצעות פורמט SDK מודולרי. אם אתם עוברים לגרסה 2.0.0 ומעלה של gcip-iap, בצעו את הפעולות הבאות:
- מעדכנים את גרסת
firebaseבקובץpackage.jsonלגרסה 9.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(). אפשר להטמיע את ה-method הזו כדי לבחור דייר באופן פרוגרמטי, או להציג ממשק משתמש כדי שהמשתמש יוכל לבחור דייר בעצמו.
בכל מקרה, הספרייה משתמשת באובייקט SelectedTenantInfo שמוחזר כדי להשלים את תהליך האימות. הוא מכיל את המזהה של הדייר שנבחר, מזהי ספקים וכתובת האימייל שהמשתמש הזין.
אם יש לכם כמה דיירים בפרויקט, אתם צריכים לבחור אחד מהם לפני שתוכלו לאמת משתמש. אם יש לכם רק דייר אחד, או אם אתם משתמשים באימות ברמת הפרויקט, אתם לא צריכים להטמיע את selectTenant().
IAP תומך באותם ספקים שנתמכים ב-Identity Platform, כמו:
- כתובת אימייל וסיסמה
- OAuth (Google, Facebook, Twitter, GitHub, Microsoft וכו')
- SAML
- OIDC
- מספר טלפון
- בהתאמה אישית
- אנונימי
אי אפשר להשתמש בסוגי אימות של מספר טלפון, אימות בהתאמה אישית ואימות אנונימי בסביבות עם כמה דיירים.
בחירת דיירים (tenants) באופן פרוגרמטי
כדי לבחור דייר באופן פרוגרמטי, צריך להשתמש בהקשר הנוכחי. הכיתה 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 ולמזהה הדייר (tenant) שסופקו. אם לא צוין מזהה דייר, צריך להשתמש בספקי זהויות ברמת הפרויקט.
השדה getAuth() עוקב אחרי המיקום שבו מאוחסן המשתמש שתואם להגדרות שצוינו. הוא גם מאפשר לרענן באופן שקט את אסימון המזהה של 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 שסופק לא תקין. |
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 |
הפעולה Web safe base64 unescape נכשלה. |
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;
});
}
אם אתם רוצים ששינויים שבוצעו במשתמש יבואו לידי ביטוי בטענות של אסימון המזהה שמופצות על ידי IAP לאפליקציה, אתם צריכים לאלץ את רענון האסימון:
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();
פורסים את האפליקציה ועוברים לדף האימות. ממשק המשתמש המותאם אישית לכניסה אמור להופיע.