פונקציות חסימה מאפשרות לך להפעיל קוד מותאם אישית שמשנה את התוצאה של רישום משתמש או כניסה לאפליקציה שלך. לדוגמה, אתה יכול למנוע ממשתמש לבצע אימות אם הוא לא עומד בקריטריונים מסוימים, או לעדכן מידע של משתמש לפני החזרתו לאפליקציית הלקוח שלך.
לפני שאתה מתחיל
כדי להשתמש בפונקציות חסימה, עליך לשדרג את פרויקט Firebase שלך ל-Firebase Authentication with Identity Platform. אם עדיין לא שדרגת, עשה זאת קודם.
הבנת פונקציות חסימה
ניתן לרשום פונקציות חסימה לשני אירועים:
beforeCreate
: מופעל לפני שמשתמש חדש נשמר במסד הנתונים של Firebase Authentication, ולפני החזרת אסימון לאפליקציית הלקוח שלך.beforeSignIn
: מופעל לאחר אימות אישורי המשתמש, אך לפני אימות Firebase מחזיר אסימון מזהה לאפליקציית הלקוח שלך. אם האפליקציה שלך משתמשת באימות רב-גורמי, הפונקציה מופעלת לאחר שהמשתמש מאמת את הגורם השני שלו. שים לב שיצירת משתמש חדש מפעילה גםbeforeSignIn
, בנוסףbeforeCreate
.
זכור את הדברים הבאים בעת שימוש בפונקציות חסימה:
הפונקציה שלך חייבת להגיב תוך 7 שניות. לאחר 7 שניות, אימות Firebase מחזיר שגיאה, ותפעול הלקוח נכשל.
קודי תגובה של HTTP שאינם
200
מועברים לאפליקציות הלקוח שלך. ודא שקוד הלקוח שלך מטפל בכל השגיאות שהפונקציה שלך יכולה להחזיר.הפונקציות חלות על כל המשתמשים בפרויקט שלך, כולל כל המשתמשים הכלולים בדייר . אימות Firebase מספק מידע על משתמשים לפונקציה שלך, כולל כל דיירים שהם שייכים אליהם, כך שתוכל להגיב בהתאם.
קישור ספק זהות אחר לחשבון מפעיל מחדש כל פונקציה שנרשמה
beforeSignIn
.אימות אנונימי והתאמה אישית אינם מפעילים פונקציות חסימה.
פרוס ורשום פונקציית חסימה
כדי להכניס את הקוד המותאם אישית שלך לזרימות אימות המשתמש, פרוס ורשום פונקציות חסימה. לאחר שפונקציות החסימה שלך נפרסות ונרשמות, הקוד המותאם אישית שלך חייב להסתיים בהצלחה כדי שהאימות ויצירת המשתמש יצליחו.
פרוס פונקציית חסימה
אתה פורס פונקציית חסימה באותו אופן כמו שאתה פורס כל פונקציה. (ראה את דף תחילת העבודה בענן לפרטים). לסיכום:
כתוב פונקציות ענן המטפלות באירוע
beforeCreate
, באירועbeforeSignIn
או שניהם.לדוגמה, כדי להתחיל, אתה יכול להוסיף את הפונקציות הבאות ללא הפעלה ל-
index.js
:const functions = require('firebase-functions'); exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => { // TODO }); exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => { // TODO });
הדוגמאות לעיל השמיטו את היישום של לוגיקת אימות מותאמת אישית. עיין בסעיפים הבאים כדי ללמוד כיצד ליישם את פונקציות החסימה שלך ותרחישים נפוצים עבור דוגמאות ספציפיות.
פרוס את הפונקציות שלך באמצעות Firebase CLI:
firebase deploy --only functions
עליך לפרוס מחדש את הפונקציות שלך בכל פעם שאתה מעדכן אותן.
רשום פונקציית חסימה
עבור לדף הגדרות אימות Firebase במסוף Firebase.
בחר בכרטיסייה פונקציות חסימה .
רשום את פונקציית החסימה שלך על ידי בחירה בה מהתפריט הנפתח תחת לפני יצירת חשבון (לפני יצירת) או לפני כניסה (לפני כניסה) .
שמור את השינויים שלך.
קבלת מידע על משתמש והקשר
האירועים beforeSignIn
ו- beforeCreate
מספקים אובייקטי User
ו- EventContext
המכילים מידע על המשתמש שנכנס. השתמש בערכים אלה בקוד שלך כדי לקבוע אם לאפשר לפעולה להמשיך.
לרשימה של מאפיינים הזמינים באובייקט User
, עיין בהפניה ל- UserRecord
API .
האובייקט EventContext
מכיל את המאפיינים הבאים:
שֵׁם | תיאור | דוגמא |
---|---|---|
locale | מקום היישום. אתה יכול להגדיר את המקום באמצעות ה-SDK של הלקוח, או על ידי העברת כותרת המקום ב-REST API. | fr או sv-SE |
ipAddress | כתובת ה-IP של המכשיר שממנו משתמש הקצה נרשם או נכנס. | 114.14.200.1 |
userAgent | סוכן המשתמש מפעיל את פונקציית החסימה. | Mozilla/5.0 (X11; Linux x86_64) |
eventId | המזהה הייחודי של האירוע. | rWsyPtolplG2TBFoOkkgyg |
eventType | סוג האירוע. זה מספק מידע על שם האירוע, כגון beforeSignIn או beforeCreate , ושיטת הכניסה המשויכת בשימוש, כמו Google או דוא"ל/סיסמה. | providers/cloud.auth/eventTypes/user.beforeSignIn:password |
authType | תמיד USER . | USER |
resource | פרויקט אימות Firebase או דייר. | projects/ project-id /tenants/ tenant-id |
timestamp | הזמן שבו האירוע הופעל, מעוצב כמחרוזת RFC 3339 . | Tue, 23 Jul 2019 21:10:57 GMT |
additionalUserInfo | אובייקט המכיל מידע על המשתמש. | AdditionalUserInfo |
credential | אובייקט המכיל מידע על האישורים של המשתמש. | AuthCredential |
חסימת רישום או כניסה
כדי לחסום רישום או ניסיון כניסה, זרוק HttpsError
בפונקציה שלך. לדוגמה:
Node.js
throw new functions.auth.HttpsError('permission-denied');
הטבלה הבאה מפרטת את השגיאות שאתה יכול להעלות, יחד עם הודעת השגיאה המוגדרת כברירת מחדל:
שֵׁם | קוד | הוֹדָעָה |
---|---|---|
invalid-argument | 400 | הלקוח ציין ארגומנט לא חוקי. |
failed-precondition | 400 | לא ניתן לבצע את הבקשה במצב המערכת הנוכחי. |
out-of-range | 400 | הלקוח ציין טווח לא חוקי. |
unauthenticated | 401 | אסימון OAuth חסר, לא חוקי או שפג תוקפו. |
permission-denied | 403 | ללקוח אין הרשאה מספקת. |
not-found | 404 | המשאב שצוין לא נמצא. |
aborted | 409 | התנגשות במקביל, כגון התנגשות קריאה-שינוי-כתיבה. |
already-exists | 409 | המשאב שלקוח ניסה ליצור כבר קיים. |
resource-exhausted | 429 | או מחוץ למכסת המשאבים או הגעה להגבלת שיעור. |
cancelled | 499 | הבקשה בוטלה על ידי הלקוח. |
data-loss | 500 | אובדן נתונים בלתי ניתן לשחזור או השחתת נתונים. |
unknown | 500 | שגיאת שרת לא ידועה. |
internal | 500 | שגיאת שרת פנימית. |
not-implemented | 501 | שיטת API לא מיושמת על ידי השרת. |
unavailable | 503 | שירותים לא זמינים. |
deadline-exceeded | 504 | חרג המועד האחרון לבקשה. |
אתה יכול גם לציין הודעת שגיאה מותאמת אישית:
Node.js
throw new functions.auth.HttpsError('permission-denied', 'Unauthorized request origin!');
הדוגמה הבאה מראה כיצד לחסום משתמשים שאינם נמצאים בדומיין מסוים מלהירשם לאפליקציה שלך:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
// (If the user is authenticating within a tenant context, the tenant ID can be determined from
// user.tenantId or from context.resource, e.g. 'projects/project-id/tenant/tenant-id-1')
// Only users of a specific domain can sign up.
if (user.email.indexOf('@acme.com') === -1) {
throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email "${user.email}"`);
}
});
לא משנה אם אתה משתמש בהודעת ברירת מחדל או מותאמת אישית, Cloud Functions עוטפת את השגיאה ומחזירה אותה ללקוח כשגיאה פנימית. לדוגמה:
throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email user@evil.com}`);
האפליקציה שלך אמורה לתפוס את השגיאה ולטפל בה בהתאם. לדוגמה:
JavaScript
// Blocking functions can also be triggered in a multi-tenant context before user creation.
// firebase.auth().tenantId = 'tenant-id-1';
firebase.auth().createUserWithEmailAndPassword('johndoe@example.com', 'password')
.then((result) => {
result.user.getIdTokenResult()
})
.then((idTokenResult) => {
console.log(idTokenResult.claim.admin);
})
.catch((error) => {
if (error.code !== 'auth/internal-error' && error.message.indexOf('Cloud Function') !== -1) {
// Display error.
} else {
// Registration succeeds.
}
});
שינוי משתמש
במקום לחסום רישום או ניסיון כניסה, אתה יכול לאפשר את המשך הפעולה, אך לשנות את אובייקט User
שנשמר במסד הנתונים של Firebase Authentication ומוחזר ללקוח.
כדי לשנות משתמש, החזר אובייקט ממטפל האירוע שלך המכיל את השדות לשינוי. אתה יכול לשנות את השדות הבאים:
-
displayName
-
disabled
-
emailVerified
-
photoURL
-
customClaims
-
sessionClaims
(beforeSignIn
בלבד)
למעט sessionClaims
, כל השדות שהשתנו נשמרים במסד הנתונים של Firebase Authentication, מה שאומר שהם כלולים באסימון התגובה ונמשכים בין הפעלות משתמש.
הדוגמה הבאה מראה כיצד להגדיר שם ברירת מחדל לתצוגה:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
return {
// If no display name is provided, set it to "Guest".
displayName: user.displayName || 'Guest';
};
});
אם אתה רושם מטפל באירועים הן עבור beforeCreate
והן beforeSignIn
, שימו לב כי beforeSignIn
מופעלת לאחר beforeCreate
. שדות משתמש שעודכנו ב- beforeCreate
גלויים ב- beforeSignIn
. אם תגדיר שדה שאינו sessionClaims
בשני מטפלי האירועים, הערך שהוגדר ב- beforeSignIn
יחליף את הערך שהוגדר ב- beforeCreate
. עבור sessionClaims
בלבד, הן מופצות לתביעות האסימונים של ההפעלה הנוכחית, אך אינן נמשכות או מאוחסנות במסד הנתונים.
לדוגמה, אם מוגדרות sessionClaims
כלשהן, beforeSignIn
יחזיר אותם עם כל תביעות beforeCreate
, והם ימוזגו. כאשר הם ממוזגים, אם מפתח sessionClaims
מתאים למפתח ב- customClaims
, ה- customClaims
התואמות יוחלפו בתביעות האסימון על ידי מפתח sessionClaims
. עם זאת, מפתח ה- customClaims
המוכר יתר על המידה עדיין יישמר במסד הנתונים עבור בקשות עתידיות.
אישורים ונתונים של OAuth נתמכים
אתה יכול להעביר אישורים ונתונים של OAuth לפונקציות חסימה מספקי זהות שונים. הטבלה הבאה מציגה אילו אישורים ונתונים נתמכים עבור כל ספק זהות:
ספק זהות | אסימון מזהה | אסימון גישה | תאריך תפוגה | סוד אסימון | רענן אסימון | תביעות כניסה |
---|---|---|---|---|---|---|
גוגל | כן | כן | כן | לא | כן | לא |
פייסבוק | לא | כן | כן | לא | לא | לא |
טוויטר | לא | כן | לא | כן | לא | לא |
GitHub | לא | כן | לא | לא | לא | לא |
מיקרוסופט | כן | כן | כן | לא | כן | לא |
לינקדאין | לא | כן | כן | לא | לא | לא |
יאהו | כן | כן | כן | לא | כן | לא |
תפוח עץ | כן | כן | כן | לא | כן | לא |
SAML | לא | לא | לא | לא | לא | כן |
OIDC | כן | כן | כן | לא | כן | כן |
רענן אסימונים
כדי להשתמש באסימון רענון בפונקציית חסימה, תחילה עליך לסמן את תיבת הסימון בדף פונקציות חסימה של מסוף Firebase.
אסימוני רענון לא יוחזרו על ידי ספקי זהות כלשהם בעת כניסה ישירה עם אישורי OAuth, כגון אסימון מזהה או אסימון גישה. במצב זה, אותו אישור OAuth בצד הלקוח יועבר לפונקציית החסימה.
הסעיפים הבאים מתארים כל סוג של ספק זהות ואת האישורים והנתונים הנתמכים שלהם.
ספקי OIDC גנריים
כאשר משתמש נכנס עם ספק OIDC גנרי, האישורים הבאים יועברו:
- ID token : מסופק אם זרימת
id_token
נבחרה. - אסימון גישה : מסופק אם זרימת הקוד נבחרה. שים לב שזרימת הקוד נתמכת כרגע רק באמצעות REST API.
- אסימון רענון : מסופק אם נבחר טווח
offline_access
.
דוגמא:
const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
גוגל
כאשר משתמש נכנס ל-Google, האישורים הבאים יועברו:
- אסימון מזהה
- אסימון גישה
- אסימון רענון : מסופק רק אם מתבקשים לקבל את הפרמטרים המותאמים אישית הבאים:
-
access_type=offline
-
prompt=consent
, אם המשתמש הסכים בעבר ולא התבקש היקף חדש
-
דוגמא:
const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({
'access_type': 'offline',
'prompt': 'consent'
});
firebase.auth().signInWithPopup(provider);
למידע נוסף על אסימוני רענון של Google .
פייסבוק
כאשר משתמש נכנס לפייסבוק, האישורים הבאים יעברו:
- אסימון גישה : מוחזר אסימון גישה שניתן להחליף באסימון גישה אחר. למד עוד על הסוגים השונים של אסימוני גישה הנתמכים על ידי פייסבוק וכיצד תוכל להחליף אותם באסימונים ארוכי חיים .
GitHub
כאשר משתמש נכנס ל-GitHub, האישורים הבאים יעברו:
- אסימון גישה : לא יפוג אלא אם כן בוטל.
מיקרוסופט
כאשר משתמש נכנס ל-Microsoft, האישורים הבאים יועברו:
- אסימון מזהה
- אסימון גישה
- אסימון רענון : הועבר לפונקציית החסימה אם נבחר טווח
offline_access
.
דוגמא:
const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);
יאהו
כאשר משתמש נכנס ל-Yahoo, האישורים הבאים יועברו ללא פרמטרים או היקפים מותאמים אישית:
- אסימון מזהה
- אסימון גישה
- רענן אסימון
לינקדאין
כאשר משתמש נכנס ל-LinkedIn, האישורים הבאים יעברו:
- אסימון גישה
תפוח עץ
כאשר משתמש נכנס ל-Apple, האישורים הבאים יועברו ללא פרמטרים או היקפים מותאמים אישית:
- אסימון מזהה
- אסימון גישה
- רענן אסימון
תרחישים נפוצים
הדוגמאות הבאות מדגימות כמה מקרי שימוש נפוצים לחסימת פונקציות:
מאפשר רישום מדומיין ספציפי בלבד
הדוגמה הבאה מראה כיצד למנוע ממשתמשים שאינם חלק מהדומיין example.com
להירשם לאפליקציה שלך:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (!user.email || user.email.indexOf('@example.com') === -1) {
throw new functions.auth.HttpsError(
'invalid-argument', `Unauthorized email "${user.email}"`);
}
});
חסימת משתמשים עם אימיילים לא מאומתים מההרשמה
הדוגמה הבאה מראה כיצד למנוע ממשתמשים עם אימיילים לא מאומתים להירשם לאפליקציה שלך:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (user.email && !user.emailVerified) {
throw new functions.auth.HttpsError(
'invalid-argument', `Unverified email "${user.email}"`);
}
});
דורש אימות דוא"ל בהרשמה
הדוגמה הבאה מראה כיצד לדרוש ממשתמש לאמת את הדוא"ל שלו לאחר ההרשמה:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
const locale = context.locale;
if (user.email && !user.emailVerified) {
// Send custom email verification on sign-up.
return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
return sendCustomVerificationEmail(user.email, link, locale);
});
}
});
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (user.email && !user.emailVerified) {
throw new functions.auth.HttpsError(
'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
}
});
התייחסות לאימיילים מסוימים של ספקי זהות כמאומתים
הדוגמה הבאה מראה כיצד להתייחס לאימיילים של משתמשים מספקי זהות מסוימים כמאומתים:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (user.email && !user.emailVerified && context.eventType.indexOf(':facebook.com') !== -1) {
return {
emailVerified: true,
};
}
});
חסימת כניסה מכתובות IP מסוימות
הדוגמה הבאה כיצד לחסום כניסה מטווחי כתובות IP מסוימים:
Node.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (isSuspiciousIpAddress(context.ipAddress)) {
throw new functions.auth.HttpsError(
'permission-denied', 'Unauthorized access!');
}
});
הגדרת תביעות מותאמות אישית והפעלה
הדוגמה הבאה מראה כיצד להגדיר תביעות מותאמות אישית וביקורות:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (context.credential &&
context.credential.providerId === 'saml.my-provider-id') {
return {
// Employee ID does not change so save in persistent claims (stored in
// Auth DB).
customClaims: {
eid: context.credential.claims.employeeid,
},
// Copy role and groups to token claims. These will not be persisted.
sessionClaims: {
role: context.credential.claims.role,
groups: context.credential.claims.groups,
}
}
}
});
מעקב אחר כתובות IP כדי לנטר פעילות חשודה
אתה יכול למנוע גניבת אסימון על ידי מעקב אחר כתובת ה-IP שממנה משתמש נכנס, והשוואתה לכתובת ה-IP בבקשות עוקבות. אם הבקשה נראית חשודה - למשל, כתובות ה-IP הן מאזורים גיאוגרפיים שונים - תוכל לבקש מהמשתמש להיכנס שוב.
השתמש בתביעות הפעלה כדי לעקוב אחר כתובת ה-IP שאיתה המשתמש נכנס:
Node.js
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => { return { sessionClaims: { signInIpAddress: context.ipAddress, }, }; });
כאשר משתמש מנסה לגשת למשאבים הדורשים אימות באמצעות Firebase Authentication, השווה את כתובת ה-IP בבקשה עם ה-IP המשמש לכניסה:
Node.js
app.post('/getRestrictedData', (req, res) => { // Get the ID token passed. const idToken = req.body.idToken; // Verify the ID token, check if revoked and decode its payload. admin.auth().verifyIdToken(idToken, true).then((claims) => { // Get request IP address const requestIpAddress = req.connection.remoteAddress; // Get sign-in IP address. const signInIpAddress = claims.signInIpAddress; // Check if the request IP address origin is suspicious relative to // the session IP addresses. The current request timestamp and the // auth_time of the ID token can provide additional signals of abuse, // especially if the IP address suddenly changed. If there was a sudden // geographical change in a short period of time, then it will give // stronger signals of possible abuse. if (!isSuspiciousIpAddressChange(signInIpAddress, requestIpAddress)) { // Suspicious IP address change. Require re-authentication. // You can also revoke all user sessions by calling: // admin.auth().revokeRefreshTokens(claims.sub). res.status(401).send({error: 'Unauthorized access. Please login again!'}); } else { // Access is valid. Try to return data. getData(claims).then(data => { res.end(JSON.stringify(data); }, error => { res.status(500).send({ error: 'Server error!' }) }); } }); });
הקרנת תמונות משתמשים
הדוגמה הבאה מראה כיצד לחטא תמונות פרופיל של משתמשים:
Node.js
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (user.photoURL) {
return isPhotoAppropriate(user.photoURL)
.then((status) => {
if (!status) {
// Sanitize inappropriate photos by replacing them with guest photos.
// Users could also be blocked from sign-up, disabled, etc.
return {
photoURL: PLACEHOLDER_GUEST_PHOTO_URL,
};
}
});
});
למידע נוסף על איך לזהות ולחטא תמונות, עיין בתיעוד של Cloud Vision .
גישה לאישורי OAuth של ספק הזהות של משתמש
הדוגמה הבאה מדגימה כיצד להשיג אסימון רענון עבור משתמש שנכנס ל-Google, ולהשתמש בו כדי לקרוא לממשקי ה-API של יומן Google. אסימון הרענון נשמר לגישה לא מקוונת.
Node.js
const {OAuth2Client} = require('google-auth-library');
const {google} = require('googleapis');
// ...
// Initialize Google OAuth client.
const keys = require('./oauth2.keys.json');
const oAuth2Client = new OAuth2Client(
keys.web.client_id,
keys.web.client_secret
);
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (context.credential &&
context.credential.providerId === 'google.com') {
// Store the refresh token for later offline use.
// These will only be returned if refresh tokens credentials are included
// (enabled by Cloud console).
return saveUserRefreshToken(
user.uid,
context.credential.refreshToken,
'google.com'
)
.then(() => {
// Blocking the function is not required. The function can resolve while
// this operation continues to run in the background.
return new Promise((resolve, reject) => {
// For this operation to succeed, the appropriate OAuth scope should be requested
// on sign in with Google, client-side. In this case:
// https://www.googleapis.com/auth/calendar
// You can check granted_scopes from within:
// context.additionalUserInfo.profile.granted_scopes (space joined list of scopes).
// Set access token/refresh token.
oAuth2Client.setCredentials({
access_token: context.credential.accessToken,
refresh_token: context.credential.refreshToken,
});
const calendar = google.calendar('v3');
// Setup Onboarding event on user's calendar.
const event = {/** ... */};
calendar.events.insert({
auth: oauth2client,
calendarId: 'primary',
resource: event,
}, (err, event) => {
// Do not fail. This is a best effort approach.
resolve();
});
});
})
}
});