במאמר הזה מוסבר איך ליצור דף אימות משלכם באמצעות זהויות חיצוניות ו-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 גרסה 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 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() עוקב אחרי המקום שבו מאוחסן המשתמש שתואם להגדרה שסופקה. בנוסף, אפשר לרענן באופן שקוף את טוקן הזהות של משתמש שאומת בעבר ב-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 |
ביטול האסקייפ של 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;
});
}
אם רוצים ששינויים שבוצעו במשתמש יבואו לידי ביטוי בהצהרות של טוקן של מזהה שמועבר מ-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();
מבצעים פריסה של האפליקציה ועוברים לדף האימות. אמור להופיע ממשק משתמש מותאם אישית לכניסה לחשבון.
המאמרים הבאים
- איך ניגשים למשאבים שלא שייכים ל-Google באמצעות תוכנה
- מידע נוסף על ניהול סשנים
- איך זה עובד עם זהויות חיצוניות ב-IAP