מפתחות גישה הופכים את חשבונות המשתמשים לבטוחים יותר, פשוטים יותר וקלים יותר לשימוש.
השימוש במפתחות גישה משפר את האבטחה, מפשט את הכניסות לחשבון ומחליף את הסיסמאות. בניגוד לסיסמאות רגילות, שהמשתמשים צריכים לזכור ולהזין באופן ידני, מפתחות גישה מתבססים על מנגנוני נעילת המסך של המכשיר, כמו נתונים ביומטריים או קודי אימות, ומפחיתים את הסיכון לפישינג ולגניבה של פרטי הכניסה.
מפתחות הגישה מסתנכרנים בין המכשירים באמצעות ספקי מפתחות גישה כמו מנהל הסיסמאות של Google ו-iCloud Keychain.
צריך ליצור מפתח גישה, שבו המפתח הפרטי מאוחסן באופן מאובטח אצל ספק מפתח הגישה, יחד עם המטא-נתונים הנדרשים והמפתח הציבורי שלו שמאוחסן בשרת לצורך אימות. המפתח הפרטי מנפיק חתימה אחרי אימות המשתמש בדומיין הנכון, וכך מפתחות הגישה עמידים בפני פישינג. המפתח הציבורי מאמת את החתימה בלי לאחסן פרטי כניסה רגישים, כך שמפתחות הגישה עמידים בפני גניבת פרטי כניסה.
איך יוצרים מפתח גישה
כדי שמשתמש יוכל להיכנס באמצעות מפתח גישה, צריך ליצור את מפתח הגישה, לשייך אותו לחשבון משתמש ולאחסן את המפתח הציבורי שלו בשרת.
אתם יכולים לבקש מהמשתמשים ליצור מפתח גישה באחת מהסיטואציות הבאות:
- במהלך ההרשמה או אחריה.
- אחרי הכניסה לחשבון.
- אחרי שנכנסים לחשבון באמצעות מפתח גישה ממכשיר אחר (כלומר, הערך של
[authenticatorAttachment](https://web.dev/articles/passkey-form-autofill#authenticator-attachment)
הואcross-platform
). - בדף ייעודי שבו המשתמשים יכולים לנהל את מפתחות הגישה שלהם.
כדי ליצור מפתח גישה, משתמשים ב-WebAuthn API.
ארבעת הרכיבים בתהליך הרישום של מפתח הגישה הם:
- קצה עורפי: אחסון פרטי חשבון המשתמש, כולל המפתח הציבורי.
- חזית: מתקשרת עם הדפדפן ומאחזרת את הנתונים הנדרשים מהקצה העורפי.
- דפדפן: מפעיל את ה-JavaScript ומקיים אינטראקציה עם WebAuthn API.
- ספק מפתח הגישה: יוצר את מפתח הגישה ושומר אותו. בדרך כלל מדובר במנהל סיסמאות כמו מנהל הסיסמאות של Google או מפתח אבטחה.

לפני שיוצרים מפתח גישה, צריך לוודא שהמערכת עומדת בדרישות המוקדמות הבאות:
חשבון המשתמש מאומת באמצעות שיטה מאובטחת (למשל, אימות באימייל, אימות בטלפון או איחוד שירותי אימות הזהות) בחלון זמן קצר.
ממשק הקצה (frontend) והקצה העורפי (backend) יכולים לתקשר בצורה מאובטחת כדי להחליף נתוני פרטי כניסה.
הדפדפן תומך ב-WebAuthn וביצירת מפתחות גישה.
בחלקים הבאים נסביר איך לבדוק את רוב הגורמים האלה.
אחרי שהמערכת עומדת בתנאים האלה, מתבצע התהליך הבא ליצירת מפתח גישה:
- המערכת מפעילה את תהליך יצירת מפתח הגישה כשהמשתמש מתחיל את הפעולה (לדוגמה, לוחץ על הלחצן 'יצירת מפתח גישה' בדף ניהול מפתחות הגישה או אחרי שהוא מסיים את ההרשמה).
- ממשק הקצה מבקש מנתוני הקצה העורפי את פרטי הכניסה הנדרשים, כולל פרטי המשתמש, אתגר ומזהי פרטי הכניסה, כדי למנוע כפילויות.
- ממשק הקצה קורא ל-
navigator.credentials.create()
כדי לבקש מספק מפתח הגישה של המכשיר ליצור מפתח גישה באמצעות המידע מ-back-end. שימו לב שהקריאה הזו מחזירה הבטחה (promise). - המכשיר של המשתמש מאמת אותו באמצעות שיטה ביומטרית, קוד אימות או קו ביטול נעילה כדי ליצור את מפתח הגישה.
- ספק מפתח הגישה יוצר מפתח גישה ומחזיר פרטי כניסה של מפתח ציבורי לקצה הקדמי, ומבטל את ההבטחה.
- חזית המערכת שולחת את פרטי הכניסה של המפתח הציבורי שנוצר לקצה העורפי.
- הקצה העורפי מאחסן את המפתח הציבורי ונתונים חשובים אחרים לאימות עתידי.
- הקצה העורפי שולח התראה למשתמש (לדוגמה, באימייל) כדי לאשר את יצירת מפתח הגישה ולזהות גישה לא מורשית פוטנציאלית.
התהליך הזה מבטיח תהליך רישום מאובטח וחלק של מפתחות הגישה למשתמשים.
תאימות
רוב הדפדפנים תומכים ב-WebAuthn, עם כמה פערים קטנים. באתר passkeys.dev מפורטים פרטי התאימות לדפדפנים ולמערכות הפעלה.
יצירת מפתח גישה חדש
כדי ליצור מפתח גישה חדש, זה התהליך שצריך לבצע בחזית:
- בודקים את התאימות.
- אחזור מידע מהקצה העורפי
- קוראים ל-WebAuth API כדי ליצור מפתח גישה.
- שולחים את המפתח הציבורי שהוחזר לקצה העורפי.
- שומרים את פרטי הכניסה.
בקטעים הבאים מוסבר איך לעשות זאת.
בדיקת התאימות
לפני הצגת הלחצן 'יצירת מפתח גישה חדש', ממשק הקצה צריך לבדוק אם:
- הדפדפן תומך ב-WebAuthn באמצעות
PublicKeyCredential
.
- המכשיר תומך במאמת פלטפורמה (יכול ליצור מפתח גישה ולבצע אימות באמצעות מפתח הגישה) באמצעות
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.
- הדפדפן תומך בממשק משתמש מותנה של WebAuthn באמצעות
PublicKeyCredenital.isConditionalMediationAvailable()
.
בקטע הקוד הבא מוסבר איך לבדוק את התאימות לפני שמוצגות האפשרויות שקשורות למפתח הגישה.
// Availability of `window.PublicKeyCredential` means WebAuthn is usable.
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.
// `isConditionalMediationAvailable` means the feature detection is usable.
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
PublicKeyCredential.isConditionalMediationAvailable) {
// Check if user verifying platform authenticator is available.
Promise.all([
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
PublicKeyCredential.isConditionalMediationAvailable(),
]).then(results => {
if (results.every(r => r === true)) {
// Display "Create a new passkey" button
}
});
}
בדוגמה הזו, הלחצן Create a new passkey אמור להופיע רק אם כל התנאים מתקיימים.
אחזור מידע מהקצה העורפי
כשהמשתמש לוחץ על הלחצן, מאחזרים את המידע הנדרש מהקצה העורפי כדי לבצע קריאה ל-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 (מזהה צד נסמך), דומיין ואתר יכולים לציין את הדומיין שלהם או סיומת שניתן לרשום. לדוגמה, אם המקור של RP הואhttps://login.example.com:1337
, מזהה ה-RP יכול להיותlogin.example.com
אוexample.com
. אם מזהה RP מצוין כ-example.com
, המשתמש יכול לבצע אימות ב-login.example.com
או בכל תת-דומיין ב-example.com
. למידע נוסף, ראו איך מאפשרים שימוש חוזר במפתח גישה באתרים שונים באמצעות בקשות מקור קשורות.rp.name
: השם של הצד הנסמך (RP). הוא הוצא משימוש ב-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
: רשימה של מזהי פרטי כניסה שכבר רשומים. מניעת רישום של אותו מכשיר פעמיים על ידי מתן רשימה של מזהי פרטי הכניסה שכבר רשומים. אם הרכיבtransports
מסופק, הוא צריך להכיל את התוצאה של הקריאה ל-getTransports()
במהלך הרישום של כל פרטי הכניסה.authenticatorSelection.authenticatorAttachment
: מגדירים את הערך הזה כ-"platform"
יחד עםhint: ['client-device']
אם יצירת מפתח הגישה היא שדרוג מסיסמה, למשל במבצע אחרי כניסה לחשבון. הערך"platform"
מציין שה-RP רוצה מאמת פלטפורמה (מאמת שמוטמע במכשיר הפלטפורמה) שלא מציג, למשל, בקשה להכניס מפתח אבטחה מסוג USB. למשתמש יש אפשרות פשוטה יותר ליצור מפתח גישה.authenticatorSelection.requireResidentKey
: מגדירים אותו כערך בוליאניtrue
. פרטי כניסה שגלויים לכולם (מפתח מקומי) שומרים את פרטי המשתמש במפתח הגישה ומאפשרים למשתמשים לבחור את החשבון לאחר האימות.authenticatorSelection.userVerification
: הערך הזה מציין אם אימות המשתמש באמצעות נעילת המסך של המכשיר הוא"required"
, "preferred"
או"discouraged"
. ברירת המחדל היא"preferred"
, כלומר האימות עשוי לדלג על אימות המשתמש. מגדירים את הערך הזה כ-"preferred"
או משמיטים את הנכס.
מומלץ ליצור את האובייקט בשרת, לקודד את ArrayBuffer באמצעות Base64URL ולאחזר אותו מהחזית. כך תוכלו לפענח את עומס העבודה באמצעות 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 מחזיר הבטחה, וממתין לאינטראקציה של המשתמש כדי להציג תיבת דו-שיח מודלית.
// Invoke WebAuthn to create a passkey.
const credential = await navigator.credentials.create({
publicKey: options
});
שליחת פרטי הכניסה של המפתח הציבורי שהוחזר לקצה העורפי
אחרי האימות של המשתמש באמצעות נעילת המסך של המכשיר, נוצר מפתח גישה וההבטחה מתקבלת חזרה עם אובייקט PublicKeyCredential לקצה הקדמי.
יש סיבות שונות לדחייה של ההבטחה. כדי לטפל בשגיאות האלה, אפשר לבדוק את המאפיין name
של האובייקט Error
:
InvalidStateError
: כבר יש מפתח גישה במכשיר. לא תוצג למשתמש תיבת דו-שיח עם הודעת שגיאה. האתר לא אמור להתייחס לכך כאל שגיאה. המשתמש רצה שהמכשיר המקומי יירשם, והוא נרשם.NotAllowedError
: המשתמש ביטל את הפעולה.AbortError
: הפעולה בוטלה.- חריגות אחרות: קרה משהו לא צפוי. הדפדפן מציג למשתמש תיבת דו-שיח עם הודעת שגיאה.
אובייקט פרטי הכניסה של המפתח הציבורי מכיל את המאפיינים הבאים:
id
: מזהה בקידוד Base64URL של מפתח הגישה שנוצר. המזהה הזה עוזר לדפדפן לקבוע אם מפתח גישה תואם נמצא במכשיר במהלך האימות. צריך לאחסן את הערך הזה במסד הנתונים בקצה העורפי.rawId
: גרסה של מזהה פרטי הכניסה ב-ArrayBuffer.response.clientDataJSON
: נתוני לקוח שמקודדים ב-ArrayBuffer.response.attestationObject
: אובייקט אימות (attestation) בקידוד 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 name: השם של פרטי הכניסה. נותנים לו שם לפי ספק מפתח הגישה שיצר אותו, שניתן לזהות על סמך ה-AAGUID.
- מזהה משתמש: מזהה המשתמש ששימש ליצירת מפתח הגישה.
- מפתח ציבורי: המפתח הציבורי שהוחזר עם פרטי הכניסה של המפתח הציבורי. הדבר נדרש כדי לאמת טענת נכוֹנוּת (assertion) של מפתח גישה.
- תאריך ושעת יצירה: מתעדים את התאריך והשעה של יצירת מפתח הגישה. זה שימושי לזיהוי מפתח הגישה.
- תאריך ושעה של השימוש האחרון: מתועדים התאריך והשעה האחרונים שבהם המשתמש השתמש במפתח הגישה כדי להיכנס לחשבון. המידע הזה שימושי כדי לקבוע באיזה מפתח גישה המשתמש השתמש (או לא השתמש).
- 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()
. - לשלוח התראה למשתמש אחרי יצירת מפתח גישה לרישום בחשבון שלו.
משאבים
- רישום מפתח גישה בצד השרת
- מסמך של Apple: אימות משתמש באמצעות שירות אינטרנט
- מסמך של Google: כניסה ללא סיסמה באמצעות מפתחות גישה
השלב הבא: כניסה לחשבון באמצעות מפתח גישה דרך מילוי אוטומטי של טפסים.