מפתחות גישה הופכים את חשבונות המשתמשים לבטוחים יותר, פשוטים יותר ונוחים יותר לשימוש.
פורסם: 12 באוקטובר 2022, עדכון אחרון: 9 באפריל 2026
שימוש במפתחות גישה משפר את האבטחה, מפשט את הכניסה לחשבון ומחליף את הסיסמאות. בניגוד לסיסמאות רגילות, שהמשתמשים צריכים לזכור ולהזין באופן ידני, מפתחות גישה משתמשים במנגנונים לביטול נעילת המסך של המכשיר, כמו נתונים ביומטריים או קודי אימות, ומפחיתים את הסיכונים לפישינג ולגניבת פרטי כניסה.
מפתחות הגישה מסתנכרנים בין המכשירים באמצעות ספקי מפתחות גישה כמו מנהל הסיסמאות של Google ו-iCloud Keychain.
צריך ליצור מפתח גישה, לאחסן את המפתח הפרטי בצורה מאובטחת אצל ספק מפתח הגישה, יחד עם מטא נתונים נחוצים, ולאחסן את המפתח הציבורי בשרת לצורך אימות. המפתח הפרטי מנפיק חתימה אחרי אימות המשתמש בדומיין התקין, וכך מונע פישינג של מפתחות גישה. המפתח הציבורי מאמת את החתימה בלי לשמור פרטי כניסה רגישים, ולכן מפתחות גישה עמידים בפני גניבת פרטי כניסה.
איך יוצרים מפתח גישה
כדי שמשתמש יוכל להיכנס באמצעות מפתח גישה, צריך ליצור את מפתח הגישה, לשייך אותו לחשבון משתמש ולאחסן את המפתח הציבורי שלו בשרת.
כדאי לבקש מהמשתמשים ליצור מפתח גישה באחד מהמקרים הבאים:
- במהלך ההרשמה או אחריה.
- אחרי הכניסה לחשבון.
- אחרי שנכנסים לחשבון באמצעות מפתח גישה ממכשיר אחר (כלומר,
authenticatorAttachmentהואcross-platform). - בדף ייעודי שבו המשתמשים יכולים לנהל את מפתחות הגישה שלהם.
כדי ליצור מפתח גישה, משתמשים ב-WebAuthn API.
ארבעת הרכיבים של תהליך הרישום של מפתח הגישה הם:
- קצה עורפי: מאחסן את פרטי חשבון המשתמש, כולל המפתח הציבורי.
- חלק חזיתי (Frontend): מתקשר עם הדפדפן ומביא נתונים נחוצים מהחלק האחורי (Backend).
- דפדפן: מריץ את JavaScript ומתקשר עם WebAuthn API.
- ספק מפתחות גישה: יוצר ושומר את מפתח הגישה. בדרך כלל מדובר במנהל סיסמאות כמו מנהל הסיסמאות של Google, או במפתח אבטחה.
לפני שיוצרים מפתח גישה, צריך לוודא שהמערכת עומדת בדרישות המוקדמות הבאות:
חשבון המשתמש מאומת בשיטה מאובטחת (לדוגמה, אימות באמצעות אימייל, טלפון או איחוד זהויות) בפרק זמן קצר.
החלק הקדמי והבק-אנד יכולים לתקשר בצורה מאובטחת כדי להחליף נתוני פרטי כניסה.
הדפדפן תומך ב-WebAuthn וביצירת מפתחות גישה.
בקטעים הבאים נסביר איך לבדוק את רובם.
אחרי שהמערכת עומדת בתנאים האלה, התהליך הבא מתרחש כדי ליצור מפתח גישה:
- המערכת מפעילה את תהליך יצירת מפתח הגישה כשהמשתמש מתחיל את הפעולה (לדוגמה, לחיצה על הלחצן 'יצירת מפתח גישה' בדף ניהול מפתחות הגישה או אחרי סיום ההרשמה).
- החלק הקדמי של האפליקציה שולח לחלק האחורי בקשה לנתוני האישורים הנדרשים, כולל פרטי המשתמש, אתגר ומזהי אישורים, כדי למנוע כפילויות.
- הקצה הקדמי קורא ל-
navigator.credentials.create()כדי להנחות את ספק מפתחות הגישה של המכשיר ליצור מפתח גישה באמצעות המידע מהקצה האחורי. שימו לב שהקריאה הזו מחזירה הבטחה. - המכשיר של המשתמש מאמת את המשתמש באמצעות שיטה ביומטרית, קוד אימות או קו ביטול נעילה כדי ליצור את מפתח הגישה.
- ספק מפתח הגישה יוצר מפתח גישה ומחזיר אישור מפתח ציבורי לחלק הקדמי של האתר, וכך מבצע את ההבטחה.
- הקצה הקדמי שולח את פרטי הכניסה של המפתח הציבורי שנוצר לבק-אנד.
- בקצה העורפי מאוחסן המפתח הציבורי ונתונים חשובים אחרים לצורך אימות עתידי,
- הקצה העורפי מודיע למשתמש (לדוגמה, באמצעות אימייל) כדי לאשר את יצירת מפתח הגישה ולזהות גישה פוטנציאלית לא מורשית.
התהליך הזה מבטיח תהליך רישום מאובטח וחלק של מפתחות גישה למשתמשים.
תאימות
רוב הדפדפנים תומכים ב-WebAuthn, אבל יש כמה פערים קטנים. פרטים על תאימות לדפדפנים ולמערכות הפעלה זמינים באתר passkeys.dev.
יצירת מפתח גישה חדש
כדי ליצור מפתח גישה חדש, זה התהליך שצריך לבצע בחלק הקדמי של האתר:
- בדיקת התאימות
- שליפת מידע מהקצה העורפי
- קוראים ל-WebAuth API כדי ליצור מפתח גישה.
- שולחים את המפתח הציבורי שהוחזר אל ה-Backend.
- שומרים את פרטי הכניסה.
בקטעים הבאים מוסבר איך עושים את זה.
בדיקת התאימות
לפני הצגת הלחצן 'יצירת מפתח גישה חדש', ממשק הקצה צריך לבדוק אם:
- הדפדפן תומך ב-WebAuthn עם
PublicKeyCredential.
- הדפדפן תומך בזיהוי יכולות באמצעות
PublicKeyCredential.getClientCapabilities().
הדפדפן תומך בממשק משתמש מותנה של WebAuthn עם
conditionalGet.המכשיר תומך באמצעי אימות לפלטפורמה (אפשר ליצור מפתח גישה ולאמת את המכשיר) עם
passkeyPlatformAuthenticator.
בקטע הקוד הבא אפשר לראות איך בודקים את התאימות לפני שמציגים את האפשרויות שקשורות למפתחות הגישה.
if (window.PublicKeyCredential && PublicKeyCredential.getClientCapabilities) {
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet === true &&
capabilities.passkeyPlatformAuthenticator === true) {
// The browser supports passkeys and the conditional UI.
}
}
בדוגמה הזו, הכפתור יצירת מפתח גישה חדש יוצג רק אם כל התנאים מתקיימים.
שליפת מידע מהקצה העורפי
כשמשתמש לוחץ על הלחצן, מאחזרים את המידע הנדרש מהקצה האחורי כדי להתקשר אל navigator.credentials.create().
בקטע הקוד הבא מוצג אובייקט JSON עם המידע שנדרש כדי להתקשר אל navigator.credentials.create():
// Example `PublicKeyCredentialCreationOptions` contents
{
challenge: *****,
rp: {
name: "Example",
id: "example.com",
},
user: {
id: *****,
name: "john78",
displayName: "John",
},
pubKeyCredParams: [{
alg: -7, type: "public-key"
},{
alg: -257, type: "public-key"
}],
excludeCredentials: [{
id: *****,
type: 'public-key',
transports: ['internal'],
}],
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
}
}
צמדי המפתח/ערך באובייקט מכילים את המידע הבא:
-
challenge: אתגר שנוצר על ידי השרת ב-ArrayBuffer לצורך הרישום הזה. -
rp.id: מזהה RP (מזהה צד נסמך), דומיין ואתר יכולים לציין את הדומיין שלהם או סיומת שניתן לרשום. לדוגמה, אם המקור של ספק הזהויות הואhttps://login.example.com:1337, מזהה ספק הזהויות יכול להיותlogin.example.comאוexample.com. אם מזהה ה-RP מוגדר כ-example.com, המשתמש יכול לבצע אימות ב-login.example.comאו בכל תת-דומיין ב-example.com. למידע נוסף בנושא, אפשר לעיין במאמר איך מאפשרים שימוש חוזר במפתחות גישה באתרים באמצעות בקשות ממקורות קשורים. -
rp.name: השם של הצד הנסמך. המאפיין הזה הוצא משימוש ב-WebAuthn L3, אבל הוא נכלל מסיבות של תאימות. -
user.id: מזהה משתמש ייחודי ב-ArrayBuffer, שנוצר עם יצירת החשבון. הוא צריך להיות קבוע, בניגוד לשם משתמש שאפשר לערוך. מזהה המשתמש מציין חשבון, אבל אסור שהוא יכיל פרטים אישיים מזהים (PII). סביר להניח שכבר יש לכם מזהה משתמש במערכת, אבל אם צריך, אפשר ליצור מזהה משתמש במיוחד למפתחות גישה כדי שלא יכלול פרטים אישיים מזהים. -
user.name: מזהה ייחודי של החשבון שהמשתמש יזהה, כמו כתובת האימייל או שם המשתמש שלו. השם הזה יוצג בבורר החשבונות. -
user.displayName: שם נדרש ונוח יותר למשתמשים של החשבון. הוא לא צריך להיות ייחודי ויכול להיות השם שהמשתמש בחר. אם אין באתר ערך מתאים שאפשר לכלול כאן, מעבירים מחרוזת ריקה. יכול להיות שהאפשרות הזו תוצג בבורר החשבונות, בהתאם לדפדפן. -
pubKeyCredParams: מציין את האלגוריתמים של המפתחות הציבוריים שנתמכים על ידי הצד הנסמך (RP). מומלץ להגדיר את הערך[{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]. ההגדרה הזו מציינת תמיכה ב-ECDSA עם P-256 וב-RSA PKCS#1, ותמיכה בהן מספקת כיסוי מלא. -
excludeCredentials: רשימה של מזהי פרטי כניסה שכבר רשומים. מונעת רישום כפול של אותו מכשיר על ידי הצגת רשימה של מזהי אמצעי אימות שכבר נרשמו. השדהtransportsmember, אם הוא מסופק, צריך להכיל את התוצאה של הקריאה ל-getTransports()במהלך הרישום של כל אישור. -
authenticatorSelection.authenticatorAttachment: מגדירים את הערך הזה ל-"platform"יחד עםhint: ['client-device']אם יצירת מפתח הגישה היא שדרוג מסיסמה, למשל במבצע אחרי כניסה לחשבון. "platform"מציין שספק ה-RP רוצה אמצעי אימות לפלטפורמה (אמצעי אימות שמוטמע במכשיר הפלטפורמה) שלא מציג בקשה, למשל, להכניס מפתח אבטחה ל-USB. למשתמש יש אפשרות פשוטה יותר ליצור מפתח גישה. -
authenticatorSelection.requireResidentKey: מגדירים אותו כערך בוליאניtrue. פרטי כניסה שאפשר לגלות (מפתח ששמור במכשיר) מאחסנים את פרטי המשתמש במפתח הגישה ומאפשרים למשתמשים לבחור את החשבון בזמן האימות.
authenticatorSelection.userVerification: מציין אם אימות המשתמש באמצעות נעילת המסך של המכשיר הוא"required","preferred"או"discouraged". ברירת המחדל היא"preferred", כלומר יכול להיות שאמצעי האימות ידלג על אימות המשתמש. מגדירים את הערך"preferred"או משמיטים את הנכס.
מומלץ ליצור את האובייקט בשרת, לקודד את ArrayBuffer באמצעות Base64URL ולאחזר אותו מהקצה הקדמי. כך תוכלו לפענח את מטען הייעודי (payload) באמצעות PublicKeyCredential.parseCreationOptionsFromJSON() ולהעביר אותו ישירות אל navigator.credentials.create().
בקטע הקוד הבא אפשר לראות איך מאחזרים ומפענחים את המידע שנדרש ליצירת מפתח הגישה.
// Fetch an encoded `PubicKeyCredentialCreationOptions` from the server.
const _options = await fetch('/webauthn/registerRequest');
// Deserialize and decode the `PublicKeyCredentialCreationOptions`.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseCreationOptionsFromJSON(decoded_options);
...
שליחת קריאה ל-WebAuthn API כדי ליצור מפתח גישה
מתקשרים אל navigator.credentials.create() כדי ליצור מפתח גישה חדש. ממשק ה-API מחזיר אובייקט promise, וממתין לאינטראקציה של המשתמש עם תיבת דו-שיח מודלית.
// Invoke WebAuthn to create a passkey.
const credential = await navigator.credentials.create({
publicKey: options
});
שליחת פרטי הכניסה של המפתח הציבורי שהוחזרו אל ה-backend
אחרי שהמשתמש מאומת באמצעות נעילת המסך של המכשיר, נוצר מפתח גישה וההבטחה מתקיימת ומחזירה אובייקט PublicKeyCredential לחלק הקדמי של האתר.
יכולות להיות סיבות שונות לדחייה של הבטחה. כדי לטפל בשגיאות האלה, צריך לבדוק את המאפיין name של האובייקט Error:
-
InvalidStateError: כבר קיים מפתח גישה במכשיר. לא תוצג למשתמש תיבת דו-שיח עם שגיאה. האתר לא אמור להתייחס לזה כשגיאה. המשתמש רצה לרשום את המכשיר המקומי, והוא רשום. -
NotAllowedError: המשתמש ביטל את הפעולה. -
AbortError: הפעולה בוטלה. - חריגים אחרים: קרה משהו לא צפוי. בדפדפן מוצגת למשתמש תיבת דו-שיח עם שגיאה.
אובייקט פרטי הכניסה של המפתח הציבורי מכיל את המאפיינים הבאים:
-
id: מזהה בקידוד Base64URL של מפתח הגישה שנוצר. המזהה הזה עוזר לדפדפן לקבוע אם יש במכשיר מפתח גישה תואם בזמן האימות. הערך הזה צריך להיות מאוחסן במסד הנתונים בקצה העורפי. -
rawId: גרסת ArrayBuffer של מזהה פרטי הכניסה. -
response.clientDataJSON: נתוני לקוח מקודדים מסוג ArrayBuffer. -
response.attestationObject: אובייקט אימות בקידוד ArrayBuffer. הוא מכיל מידע חשוב כמו מזהה RP, דגלים ומפתח ציבורי. -
authenticatorAttachment: הפונקציה מחזירה"platform"כשפרטי הכניסה האלה נוצרים במכשיר שתומך במפתחות גישה. -
type: השדה הזה תמיד מוגדר ל-"public-key".
מקודדים את האובייקט באמצעות השיטה .toJSON(), מבצעים סריאליזציה באמצעות JSON.stringify() ואז שולחים אותו לשרת.
...
// Encode and serialize the `PublicKeyCredential`.
const _result = credential.toJSON();
const result = JSON.stringify(_result);
// Encode and send the credential to the server for verification.
const response = await fetch('/webauthn/registerResponse', {
method: 'post',
credentials: 'same-origin',
body: result
});
...
שמירת פרטי הכניסה
אחרי קבלת אישור המפתח הציבורי בבק-אנד, מומלץ להשתמש בספרייה או בפתרון בצד השרת במקום לכתוב קוד משלכם לעיבוד אישור המפתח הציבורי.
אחר כך תוכלו לשמור את המידע שאוחזר מהאישורים במסד הנתונים לשימוש עתידי.
הרשימה הבאה כוללת מאפיינים מומלצים לשמירה:
- Credential ID: מזהה פרטי הכניסה שהוחזר עם פרטי הכניסה של המפתח הציבורי.
- שם משתמש: השם של פרטי הכניסה. נותנים לו שם לפי ספק מפתח הגישה שיצר אותו, שאפשר לזהות אותו לפי ה-AAGUID.
- מזהה משתמש: מזהה המשתמש ששימש ליצירת מפתח הגישה.
- Public key: המפתח הציבורי שמוחזר עם פרטי הכניסה של המפתח הציבורי. הפעולה הזו נדרשת כדי לאמת הצהרה של מפתח גישה.
- תאריך ושעת היצירה: התאריך והשעה שבהם נוצר מפתח הגישה. כדאי להיעזר בזה כדי לזהות את מפתח הגישה.
- התאריך והשעה האחרונים שבהם נעשה שימוש: התאריך והשעה האחרונים שבהם המשתמש השתמש במפתח הגישה כדי להיכנס לחשבון. ההגדרה הזו שימושית כדי לקבוע באיזה מפתח גישה המשתמש השתמש (או לא השתמש).
- AAGUID: מזהה ייחודי של ספק מפתחות הגישה.
- דגל הזכאות לגיבוי: true אם המכשיר עומד בדרישות לסנכרון מפתחות גישה. המידע הזה עוזר למשתמשים לזהות מפתחות גישה שאפשר לסנכרן ומפתחות גישה שקשורים למכשיר (אי אפשר לסנכרן אותם) בדף ניהול מפתחות הגישה.
הוראות מפורטות יותר זמינות במאמר בנושא רישום מפתח גישה בצד השרת
התראה אם הרישום נכשל
אם הרישום של מפתח גישה נכשל, זה עלול לבלבל את המשתמש. אם יש מפתח גישה אצל ספק מפתחות הגישה והוא זמין למשתמש, אבל המפתח הציבורי המשויך לא נשמר בצד השרת, ניסיונות הכניסה באמצעות מפתח הגישה לא יצליחו אף פעם וקשה לפתור את הבעיה. חשוב לעדכן את המשתמשים אם זה המצב.
כדי למנוע מצב כזה, אפשר לסמן מפתח גישה לא מוכר לספק מפתחות הגישה באמצעות Signal API.
באמצעות קריאה ל-PublicKeyCredential.signalUnknownCredential() עם מזהה RP ומזהה אמצעי אימות, ה-RP יכול להודיע לספק מפתחות הגישה שאמצעי האימות שצוין הוסר או לא קיים. ספק מפתח הגישה מחליט איך לטפל באות הזה, אבל אם יש תמיכה באות הזה, מפתח הגישה המשויך צפוי להימחק.
// Detect authentication failure due to lack of the credential
if (response.status === 404) {
// Feature detection
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId: "example.com",
credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA" // base64url encoded credential ID
});
} else {
// Encourage the user to delete the passkey from the password manager nevertheless.
...
}
}
מידע נוסף על Signal API זמין במאמר בנושא שמירה על עקביות של מפתחות גישה עם אישורים בשרת באמצעות Signal API.
שליחת התראה למשתמש
שליחת התראה (למשל אימייל) כשמפתח גישה נרשם עוזרת למשתמשים לזהות גישה לא מורשית לחשבון. אם תוקף יוצר מפתח גישה בלי שהמשתמש יודע על כך, מפתח הגישה נשאר זמין לשימוש לרעה בעתיד, גם אחרי שהסיסמה משתנה. ההתראה עוזרת למשתמשים להימנע מכך.
רשימת המשימות
- לפני שמאפשרים למשתמש ליצור מפתח גישה, צריך לאמת אותו (רצוי באמצעות אימייל או שיטה מאובטחת).
- כדי למנוע יצירה של מפתחות גישה כפולים לאותו ספק מפתחות גישה, משתמשים ב-
excludeCredentials. - שומרים את ה-AAGUID כדי לזהות את ספק מפתח הגישה וכדי לתת שם לפרטי הכניסה של המשתמש.
- הודעה אם ניסיון לרשום מפתח גישה נכשל עם
PublicKeyCredential.signalUnknownCredential(). - שליחת הודעה למשתמש אחרי יצירה ורישום של מפתח גישה לחשבון שלו.
משאבים
- רישום מפתחות גישה בצד השרת
- מסמך של אפל: אימות משתמש דרך שירות אינטרנט
- מסמך Google: כניסה ללא סיסמה באמצעות מפתחות גישה
השלב הבא: כניסה לחשבון באמצעות מפתח גישה דרך מילוי אוטומטי של טופס.