ליצור תהליך כניסה שמבוסס על מפתחות גישה, אבל עדיין מתאים למשתמשים קיימים עם סיסמאות.
במדריך הזה מוסבר איך להשתמש במילוי אוטומטי של טפסים כדי לאפשר למשתמשים להיכנס באמצעות מפתחות גישה וגם באמצעות סיסמאות. השימוש במילוי אוטומטי של טפסים יוצר חוויית התחברות אחידה, ומפשט את המעבר מסיסמאות לשיטת האימות המאובטחת והידידותית יותר למשתמשים באמצעות מפתחות גישה.
כאן מוסבר איך להטמיע ממשק משתמש מותנה של WebAuthn כדי לתמוך במשתמשים עם מפתחות גישה וגם במשתמשים עם סיסמאות, עם מינימום חיכוך בטפסים הקיימים לכניסה לחשבון.
למה כדאי להשתמש במילוי אוטומטי של טופס כדי להיכנס באמצעות מפתח גישה?
מפתחות גישה מאפשרים למשתמשים להיכנס לאתרים באמצעות טביעת אצבע, זיהוי פנים או קוד אימות במכשיר.
אם לכל המשתמשים היו מפתחות גישה, תהליך האימות יכול היה להיות כפתור יחיד לכניסה לחשבון. הקשה על הלחצן תאפשר למשתמש לאמת את החשבון ישירות באמצעות נעילת המסך ולהיכנס לחשבון.
עם זאת, המעבר מסיסמאות למפתחות גישה מציב אתגרים. במהלך התקופה הזו, אתרים צריכים לתמוך גם במשתמשים עם סיסמאות וגם במשתמשים עם מפתחות גישה. מצפים מהמשתמשים לזכור באילו אתרים יש מפתחות גישה ומבקשים מהם לבחור מראש שיטת כניסה, מה שיוצר חוויית משתמש גרועה.
גם מפתחות גישה הם טכנולוגיה חדשה, ולכן יכול להיות קשה להסביר אותם בצורה ברורה. ממשק המילוי האוטומטי המוכר עוזר להתמודד עם האתגר של המעבר ועם הצורך של המשתמשים להכיר את הממשק.
שימוש בממשק משתמש מותנה
כדי לתמוך ביעילות במשתמשים עם מפתחות גישה וגם במשתמשים עם סיסמאות, כדאי לכלול מפתחות גישה בהצעות למילוי אוטומטי של הטופס. הגישה הזו מבוססת על ממשק משתמש מותנה, תכונה של תקן WebAuthn.
כשהמשתמשים מתמקדים בשדה להזנת קלט של שם המשתמש, מופיעה תיבת דו-שיח של מילוי אוטומטי עם הצעות למפתחות גישה מאוחסנים וסיסמאות שמורות. המשתמש יכול לבחור מפתח גישה או סיסמה ולהמשיך להיכנס לחשבון. אם הוא בוחר מפתח גישה, הוא צריך להשתמש בשיטה לפתיחת הנעילה של המכשיר.
כך המשתמשים יכולים להיכנס לאתר שלכם באמצעות טופס הכניסה הקיים, אבל עם היתרון הנוסף של אבטחה באמצעות מפתחות גישה, אם יש להם מפתח גישה.
איך עובד אימות באמצעות מפתח גישה
כדי לבצע אימות באמצעות מפתח גישה, משתמשים ב-WebAuthn API.
ארבעת המרכיבים בתהליך אימות באמצעות מפתח גישה הם:
- קצה עורפי: מאחסן את פרטי חשבון המשתמש, כולל המפתח הציבורי.
- חלק חזיתי (Frontend): מתקשר עם הדפדפן ומביא נתונים נחוצים מהחלק האחורי (Backend).
- דפדפן: מריץ את JavaScript ומתקשר עם WebAuthn API.
- ספק מפתחות גישה: יוצר ושומר את מפתח הגישה. בדרך כלל מדובר במנהל סיסמאות כמו מנהל הסיסמאות של Google, או במפתח אבטחה.
תהליך האימות באמצעות מפתחות גישה מתבצע באופן הבא:
- המשתמש מבקר בדף הכניסה, והקצה הקדמי שולח בקשה לאתגר אימות מהבק-אנד.
- הקצה העורפי יוצר ומחזיר אתגר WebAuthn שמשויך לחשבון של המשתמש.
- ממשק הקצה קורא ל-
navigator.credentials.get()עם האתגר כדי להתחיל באימות באמצעות הדפדפן. - הדפדפן, שמתקשר עם ספק מפתחות הגישה, מבקש מהמשתמש לבחור מפתח גישה (לרוב באמצעות תיבת דו-שיח של מילוי אוטומטי שמופעלת כשמתמקדים בשדה הכניסה) ולאמת את הזהות שלו באמצעות נעילת המסך או נתונים ביומטריים במכשיר.
- אחרי אימות המשתמש, ספק מפתחות הגישה חותם על האתגר, והדפדפן מחזיר את אישור המפתח הציבורי שנוצר (כולל החתימה) לקצה קדמי.
- חזית האפליקציה שולחת את פרטי הכניסה האלה לקצה העורפי.
- הקצה העורפי מאמת את החתימה של פרטי הכניסה מול המפתח הציבורי המאוחסן של המשתמש. אם האימות מצליח, ה-backend מצרף את המשתמש לחשבון.
אימות באמצעות מפתח גישה דרך מילוי אוטומטי של טופס
כדי להתחיל באימות באמצעות מפתח גישה באמצעות מילוי אוטומטי של טופס, צריך לבצע קריאה מותנית של WebAuthn get כשטוענים את דף הכניסה. הקריאה הזו אל
navigator.credentials.get() כוללת את האפשרות mediation: 'conditional'.
בקשה מותנית ל-API של WebAuthn לא מציגה ממשק משתמש באופן מיידי.
navigator.credentials.get() במקום זאת, היא ממתינה במצב 'בהמתנה' עד שהמשתמש יוצר אינטראקציה עם ההנחיה למילוי אוטומטי של שדה שם המשתמש. אם המשתמש בוחר מפתח גישה, הדפדפן פותר את אובייקט ה-promise בהמתנה באמצעות פרטי כניסה כדי להכניס את המשתמש לחשבון, בלי להשתמש בשליחת הטופס הרגילה. אם המשתמש בוחר סיסמה במקום זאת, ההבטחה לא מתקיימת, ותהליך הכניסה הרגיל באמצעות סיסמה נמשך. לאחר מכן, הדף אחראי על כניסת המשתמש.
הוספת הערה לשדה להזנת קלט בטופס
כדי להפעיל מילוי אוטומטי של מפתחות גישה, מוסיפים את המאפיין autocomplete לשדה שם המשתמש input בטופס. צריך לכלול את הערכים username ו-webauthn כשהם מופרדים ברווחים.
<input type="text" name="username" autocomplete="username webauthn" autofocus>
הוספת autofocus לשדה הזה מפעילה אוטומטית את ההנחיה למילוי אוטומטי בטעינת הדף, ומציגה מיד את הסיסמאות ומפתחות הגישה הזמינים.
זיהוי תכונות
לפני שמפעילים קריאה מותנית ל-WebAuthn API, בודקים אם:
- הדפדפן תומך ב-WebAuthn עם
PublicKeyCredential.
- הדפדפן תומך בזיהוי יכולות באמצעות
PublicKeyCredential.getClientCapabilities().
- הדפדפן תומך בממשק משתמש מותנה של WebAuthn עם
conditionalGet.
בקטע הקוד הבא אפשר לראות איך בודקים אם הדפדפן תומך בתכונות האלה:
if (window.PublicKeyCredential && PublicKeyCredential.getClientCapabilities) {
const capabilities = await PublicKeyCredential.getClientCapabilities();
// Check if conditional mediation is available.
if (capabilities.conditionalGet === true) {
// The browser supports conditional mediation.
}
}
שליפת מידע מהקצה העורפי
הקצה העורפי צריך לספק לקצה הקדמי כמה אפשרויות כדי ליזום את השיחה עם navigator.credentials.get(). בדרך כלל, האפשרויות האלה מאוחזרות כאובייקט JSON מנקודת קצה בשרת.
מאפיינים מרכזיים באובייקט האפשרויות:
-
challenge: אתגר שנוצר על ידי השרת ב-ArrayBuffer (בדרך כלל בקידוד Base64URL להעברה ב-JSON). השלב הזה חיוני למניעת התקפות שליחה מחדש. השרת צריך ליצור אתגר חדש לכל ניסיון כניסה, ולבטל אותו אחרי זמן קצר או אם הניסיון נכשל. -
allowCredentials: מערך של מתארי פרטי כניסה. מעבירים מערך ריק. הפעולה הזו גורמת לדפדפן להציג רשימה של כל פרטי הכניסה עבורrpIdשצוין.
userVerification: מציינת את ההעדפה שלכם לאימות משתמשים, כמו דרישה לשיטה לפתיחת הנעילה של המכשיר. ערך ברירת המחדל המומלץ הוא"preferred". הערכים האפשריים הם:-
"required": המאמת צריך לבצע אימות של המשתמש (למשל באמצעות קוד אימות או נתונים ביומטריים). הפעולה נכשלת אם לא ניתן לבצע אימות. -
"preferred": אמצעי האימות מנסה לאמת את המשתמש, אבל הפעולה יכולה להצליח גם בלי האימות. -
"discouraged": אם אפשר, המאמת צריך להימנע מאימות המשתמש.
-
rpId: מזהה הצד המסתמך, בדרך כלל הדומיין של האתר (לדוגמה,example.com). הערך הזה חייב להיות זהה לערךrp.idשבו השתמשתם כשנוצר אישור מפתח הגישה.
השרת צריך ליצור את אובייקט האפשרויות הזה. ערכי ArrayBuffer (כמו challenge) חייבים לעבור קידוד Base64URL להעברה ב-JSON. בצד הלקוח, אחרי ניתוח ה-JSON, משתמשים ב-PublicKeyCredential.parseRequestOptionsFromJSON() כדי להמיר את האובייקט (כולל פענוח מחרוזות Base64URL) לפורמט ש-navigator.credentials.get() מצפה לו.
בקטע הקוד הבא אפשר לראות איך מאחזרים ומפענחים את המידע שנדרש לאימות באמצעות מפתח גישה.
// Fetch an encoded PubicKeyCredentialRequestOptions from the server.
const _options = await fetch('/webauthn/signinRequest');
// Deserialize and decode the PublicKeyCredentialRequestOptions.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseRequestOptionsFromJSON(decoded_options);
...
קוראים ל-WebAuthn API עם הדגל conditional כדי לאמת את המשתמש
אחרי שמכינים את האובייקט publicKeyCredentialRequestOptions (שנקרא options בקוד לדוגמה שבהמשך), קוראים לפונקציה navigator.credentials.get() כדי להתחיל באימות מפתח הגישה המותנה.
// To abort a WebAuthn call, instantiate an AbortController.
const abortController = new AbortController();
// Invoke WebAuthn to authenticate with a passkey.
const credential = await navigator.credentials.get({
publicKey: options,
signal: abortController.signal,
// Specify 'conditional' to activate conditional UI
mediation: 'conditional'
});
פרמטרים מרכזיים של השיחה:
-
publicKey: זה חייב להיות אובייקטpublicKeyCredentialRequestOptions(שנקראoptionsבדוגמה) שאוחזר מהשרת ועבר עיבוד בשלב הקודם. -
signal: העברת אות שלAbortController(כמוabortController.signal) מאפשרת לבטל את הבקשהget()באופן פרוגרמטי. האפשרות הזו שימושית כשרוצים להפעיל קריאה אחרת של WebAuthn. -
mediation: 'conditional': זהו הדגל החשוב שגורם לקריאה של WebAuthn להיות מותנית. הפקודה הזו אומרת לדפדפן להמתין לאינטראקציה של המשתמש עם הנחיה למילוי אוטומטי, במקום להציג מיד תיבת דו-שיח מודאלית.
שליחת פרטי הכניסה של המפתח הציבורי שהוחזרו לשרת ה-RP
Aאם המשתמש בוחר מפתח גישה ומאמת את הזהות שלו (לדוגמה, באמצעות נעילת המסך של המכשיר), ההבטחה navigator.credentials.get() מתקיימת. הפונקציה מחזירה אובייקט PublicKeyCredential לחלק הקדמי של האתר.
יכולות להיות כמה סיבות לדחיית ההבטחה. כדי לטפל בשגיאות האלה, צריך לבדוק בקוד את המאפיין name של האובייקט Error:
-
NotAllowedError: המשתמש ביטל את הפעולה, או שלא נבחר מפתח גישה. -
AbortError: הפעולה בוטלה, יכול להיות שהקוד שלכם השתמש ב-AbortController. - חריגים אחרים: אירעה שגיאה לא צפויה. בדרך כלל הדפדפן מציג למשתמש תיבת דו-שיח עם שגיאה.
האובייקט PublicKeyCredential מכיל כמה מאפיינים. מאפיינים מרכזיים שרלוונטיים לאימות:
-
id: המזהה בקידוד base64url של פרטי הכניסה המאומתים של מפתח הגישה. -
rawId: גרסת ArrayBuffer של מזהה פרטי הכניסה. -
response.clientDataJSON: ArrayBuffer של נתוני לקוח. השדה הזה מכיל מידע כמו האתגר והמקור שהשרת צריך לאמת. -
response.authenticatorData: ArrayBuffer של נתוני אמצעי האימות. השדה הזה כולל מידע כמו מזהה RP. -
response.signature: ArrayBuffer שמכיל את החתימה. הערך הזה הוא הליבה של פרטי הכניסה, והשרת שלכם צריך לאמת את החתימה הזו באמצעות המפתח הציבורי שמאוחסן עבור פרטי הכניסה . -
response.userHandle: ArrayBuffer שמכיל את מזהה המשתמש שסופק במהלך רישום מפתח הגישה. -
authenticatorAttachment: מציין אם אמצעי האימות הוא חלק ממכשיר הלקוח (platform) או חיצוני (cross-platform). יכול להיות שיהיה צירוףcross-platformאם המשתמש נכנס לחשבון באמצעות טלפון. במקרים כאלה, כדאי להציע להם ליצור מפתח גישה במכשיר הנוכחי כדי שיהיה להם נוח יותר בעתיד. -
type: השדה הזה תמיד מוגדר ל-"public-key".
כדי לשלוח את האובייקט PublicKeyCredential הזה לשרת העורפי, קודם צריך להפעיל את ה-method .toJSON(). השיטה הזו יוצרת גרסה של פרטי הכניסה שאפשר להמיר ל-JSON, ומטפלת נכון בהמרה של מאפייני ArrayBuffer (כמו rawId, clientDataJSON, authenticatorData, signature ו-userHandle) למחרוזות עם קידוד Base64URL. לאחר מכן, משתמשים ב-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/signinResponse', {
method: 'post',
credentials: 'same-origin',
body: result
});
אימות החתימה
כששרת הקצה העורפי מקבל את פרטי הכניסה של המפתח הציבורי, הוא צריך לאמת את האותנטיות שלהם. התהליך כולל:
- ניתוח נתוני פרטי הכניסה.
- המערכת מחפשת את המפתח הציבורי המאוחסן שמשויך ל-
idשל פרטי הכניסה. - אימות של
signatureשהתקבל מול המפתח הציבורי המאוחסן. - אימות נתונים אחרים, כמו האתגר והמקור.
כדי לטפל בפעולות הקריפטוגרפיה האלה בצורה מאובטחת, מומלץ להשתמש בספריית FIDO/WebAuthn בצד השרת. אפשר למצוא ספריות קוד פתוח במאגר GitHub של awesome-webauthn.
אם החתימה וכל הטענות האחרות תקפות, השרת יכול לאפשר למשתמש להיכנס. שלבים מפורטים לאימות בצד השרת זמינים במאמר בנושא אימות מפתחות גישה בצד השרת
אות אם לא נמצאו פרטי הכניסה התואמים בקצה העורפי
אם שרת הקצה העורפי לא מוצא פרטי כניסה עם מזהה תואם במהלך הכניסה, יכול להיות שהמשתמש מחק בעבר את מפתח הגישה הזה מהשרת שלכם, אבל לא מספק מפתחות הגישה שלו. חוסר התאמה כזה עלול להוביל לחוויית משתמש מבלבלת אם ספק מפתחות הגישה ימשיך להציע מפתח גישה שכבר לא פועל באתר שלכם. כדי לשפר את המצב, צריך לשלוח לספק מפתחות הגישה אות להסרת מפתח הגישה היתום.
אפשר להשתמש בשיטה PublicKeyCredential.signalUnknownCredential(), שהיא חלק מ-Webauthn Signal API, כדי להודיע לספק מפתחות הגישה שהוסרו פרטי הכניסה שצוינו או שהם לא קיימים. קוראים לשיטה הסטטית הזו בצד הלקוח אם השרת מציין (לדוגמה, באמצעות קוד סטטוס של HTTP ספציפי כמו 404) שמזהה האישורים שמוצג לא מוכר. צריך לספק לשיטה הזו את מזהה הגורם המוגבל (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.
...
}
}
אחרי האימות
בהתאם לאופן שבו המשתמש נכנס לחשבון, אנחנו מציעים תהליכים שונים.
אם המשתמש נכנס לחשבון בלי מפתח גישה
אם המשתמש נכנס לאתר שלכם בלי מפתח גישה, יכול להיות שלא רשום מפתח גישה לחשבון הזה או במכשיר הנוכחי שלו. זה הזמן המתאים לעודד את המשתמשים ליצור מפתחות גישה. כדאי לנסות את הגישות הבאות:
- שדרוג סיסמאות למפתחות גישה: שימוש ביצירה מותנית, תכונה של WebAuthn שמאפשרת לדפדפן ליצור באופן אוטומטי מפתח גישה למשתמש אחרי כניסה מוצלחת באמצעות סיסמה. השימוש במפתח גישה יכול לשפר משמעותית את תהליך היצירה. איך עוזרים למשתמשים לעבור למפתחות גישה בצורה חלקה יותר
- הצגת בקשה ליצירת מפתח גישה באופן ידני: מעודדים את המשתמשים ליצור מפתח גישה. השיטה הזו יכולה להיות יעילה אחרי שמשתמש משלים תהליך כניסה מורכב יותר, כמו אימות רב-שלבי (MFA). עם זאת, חשוב להימנע מהצגת יותר מדי הנחיות, כי הן עלולות להפריע לחוויית המשתמש".
כדי לראות איך אפשר לעודד משתמשים ליצור מפתח גישה וללמוד על שיטות מומלצות אחרות, אפשר לעיין בדוגמאות במאמר הסבר למשתמשים על מפתחות גישה.
אם המשתמש נכנס באמצעות מפתח גישה
אחרי שהמשתמשים נכנסים לחשבון באמצעות מפתח גישה, יש לכם כמה אפשרויות לשפר את חוויית השימוש שלהם ולשמור על עקביות החשבון.
עידוד יצירת מפתח גישה חדש אחרי אימות מקושר למכשיר אחר
אם משתמש נכנס לחשבון באמצעות מפתח גישה במנגנון מקושר למכשיר אחר (לדוגמה, סריקת קוד QR באמצעות הטלפון), יכול להיות שמפתח הגישה שבו הוא השתמש לא יישמר באופן מקומי במכשיר שאליו הוא נכנס. הדבר יכול להתרחש במקרים הבאים:
- יש להם מפתח גישה אבל אצל ספק מפתחות גישה שלא תומך במערכת ההפעלה או בדפדפן שבהם הם משתמשים כדי להיכנס.
- הם איבדו את הגישה לספק מפתחות הגישה במכשיר שבו הם מנסים להיכנס, אבל מפתח גישה עדיין זמין במכשיר אחר.
במצב כזה, כדאי להציג למשתמש בקשה ליצור מפתח גישה חדש במכשיר הנוכחי. כך הם לא יצטרכו לחזור על תהליך הכניסה במכשירים שונים בעתיד. כדי לקבוע אם המשתמש נכנס באמצעות מפתח גישה מקושר למכשיר אחר, בודקים את המאפיין authenticatorAttachment של פרטי הכניסה. אם הערך הוא "cross-platform", זה מציין אימות מקושר למכשיר אחר. אם כן, תסביר את היתרונות של יצירת מפתח גישה חדש ותדריך אותו בתהליך היצירה.
סנכרון פרטי מפתח הגישה עם הספק באמצעות אותות
כדי להבטיח עקביות וחוויית משתמש טובה יותר, הצד המסתמך (RP) יכול להשתמש ב-WebAuthn Signals API כדי להעביר עדכונים לגבי פרטי הכניסה ופרטי המשתמש לספק מפתחות הגישה.
לדוגמה, כדי שהרשימה של ספק מפתחות הגישה תהיה מדויקת, צריך לסנכרן את פרטי הכניסה בשרת העורפי. אתם יכולים לסמן שמפתח גישה כבר לא קיים כדי שספקי מפתחות הגישה יוכלו להסיר מפתחות גישה מיותרים.
באופן דומה, אתם יכולים לציין אם משתמש עדכן את שם המשתמש או את השם המוצג שלו בשירות שלכם, כדי לעזור לשמור על עדכניות של פרטי המשתמש שמוצגים על ידי ספק מפתחות הגישה (לדוגמה, בתיבות דו-שיח לבחירת חשבון).
כדי לקרוא מידע נוסף על שיטות מומלצות לשמירה על עקביות של מפתחות גישה, אפשר לעיין במאמר שמירה על עקביות של מפתחות גישה עם פרטי הכניסה בשרת באמצעות Signal API.
לא לבקש גורם אימות נוסף
מפתחות גישה מציעים הגנה חזקה ומובנית מפני איומים נפוצים כמו פישינג. לכן, גורם אימות שני לא מוסיף ערך אבטחתי משמעותי. במקום זאת, היא יוצרת שלב מיותר למשתמשים במהלך הכניסה.
רשימת המשימות
- לאפשר למשתמשים להיכנס באמצעות מפתח גישה דרך מילוי אוטומטי של טופס.
- אותת כשלא נמצאים פרטי כניסה תואמים למפתח גישה בקצה העורפי.
- הצגת בקשה למשתמשים ליצור מפתח גישה באופן ידני אם הם עדיין לא יצרו מפתח גישה אחרי הכניסה.
- יצירה אוטומטית של מפתח גישה (יצירה מותנית) אחרי שהמשתמש נכנס לחשבון עם סיסמה (וגורם אימות נוסף).
- הצגת הנחיה ליצירת מפתח גישה מקומי אם המשתמש נכנס באמצעות מפתח גישה מקושר למכשיר אחר.
- לאחר הכניסה לחשבון או כשמתבצעים שינויים, המערכת מעבירה לספק את רשימת מפתחות הגישה הזמינים ואת פרטי המשתמש המעודכנים (שם המשתמש, השם המוצג).