איך נכנסים לחשבון עם מפתח גישה דרך מילוי אוטומטי של טפסים

יוצרים תהליך כניסה שמשתמש במפתחות גישה, תוך התחשבות במשתמשים שכבר משתמשים בסיסמה.

במדריך הזה נסביר איך להשתמש במילוי אוטומטי של טפסים כדי לאפשר למשתמשים להיכנס לחשבון באמצעות מפתחות גישה לצד סיסמאות. שימוש במילוי אוטומטי של טפסים יוצר חוויית כניסה מאוחדת, ומקל על המעבר מסיסמה לשיטת אימות מאובטחת ונוחה יותר באמצעות מפתח גישה.

איך מטמיעים את ממשק המשתמש המותנה של WebAuthn כדי לתמוך גם במשתמשי מפתחות גישה וגם במשתמשי סיסמה, עם חיכוך מינימלי בטפסים הקיימים של כניסה.

למה כדאי להשתמש במילוי אוטומטי של טפסים כדי להיכנס לחשבון באמצעות מפתח גישה?

מפתחות גישה מאפשרים למשתמשים להיכנס לאתרים באמצעות טביעת אצבע, זיהוי פנים או קוד אימות של המכשיר.

אם לכל המשתמשים היו מפתחות גישה, תהליך האימות יכול היה להיות לחצן כניסה יחיד. הקשה על הלחצן תאפשר למשתמש לאמת את החשבון ישירות באמצעות נעילת המסך ולהיכנס אליו.

עם זאת, המעבר מסיסמה למפתח גישה מציב אתגרים. במהלך התקופה הזו, אתרים צריכים לתמוך גם במשתמשים עם סיסמה וגם במשתמשים עם מפתח גישה. אם תצפו מהמשתמשים לזכור אילו אתרים משתמשים במפתחות גישה ותבקשו מהם לבחור מראש שיטת כניסה, חוויית המשתמש תהיה גרועה.

מפתחות הגישה הם גם טכנולוגיה חדשה, וקשה להסביר אותם בבירור. השימוש בממשק המוכּר של המילוי האוטומטי עוזר להתמודד עם האתגר של המעבר ועם הצורך של המשתמשים בהכרות עם הממשק.

שימוש בממשק משתמש מותנה

כדי לתמוך ביעילות גם במשתמשים שמשתמשים במפתחות גישה וגם במשתמשים שמשתמשים בסיסמאות, כדאי לכלול מפתחות גישה בהצעות המילוי האוטומטי של הטפסים. הגישה הזו מתבססת על ממשק משתמש מותנה, תכונה של תקן WebAuthn.

דוגמה לבחירת מפתח גישה באמצעות מילוי אוטומטי של טפסים.

כשהמשתמש מתמקד בשדה הקלט של שם המשתמש, תיבת דו-שיח של מילוי אוטומטי מופיעה עם הצעות למפתחות גישה שנשמרו לצד סיסמאות שנשמרו. המשתמש יכול לבחור מפתח גישה או סיסמה ולהמשיך להיכנס לחשבון, באמצעות נעילת המסך של המכשיר אם הוא בחר מפתח גישה.

כך המשתמשים יכולים להיכנס לאתר באמצעות טופס הכניסה הקיים, אבל עם היתרון הנוסף של אבטחת מפתחות הגישה, אם יש להם מפתח כזה.

איך פועל האימות באמצעות מפתח גישה

כדי לבצע אימות באמצעות מפתח גישה, משתמשים ב-WebAuthn API.

ארבעת הרכיבים בתהליך האימות באמצעות מפתח גישה הם:

  • קצה עורפי: אחסון פרטי חשבון המשתמש, כולל המפתח הציבורי.
  • חזית: מתקשרת עם הדפדפן ומאחזרת את הנתונים הנחוצים מהקצה העורפי.
  • דפדפן: מפעיל את ה-JavaScript ומקיים אינטראקציה עם WebAuthn API.
  • ספק מפתח הגישה: יוצר את מפתח הגישה ושומר אותו. בדרך כלל מדובר במנהל סיסמאות כמו מנהל הסיסמאות של Google או מפתח אבטחה.
תהליך האימות של מפתח הגישה, שבו מוצגת האינטראקציה בין הקצה הקדמי, הקצה העורפי, הדפדפן וספק מפתח הגישה.
תהליך אימות מלא של מפתח גישה.

תהליך האימות של מפתחות הגישה מתבצע לפי התהליך הבא:

  1. המשתמש נכנס לדף הכניסה, וחזית האתר מבקשת מהקצה העורפי אתגר אימות.
  2. הקצה העורפי יוצר אתגר WebAuthn ומחזיר אותו, המשויך לחשבון של המשתמש.
  3. ממשק הקצה קורא ל-navigator.credentials.get() עם האתגר כדי להתחיל את האימות באמצעות הדפדפן.
  4. הדפדפן, שמקיים אינטראקציה עם ספק מפתח הגישה, מבקש מהמשתמש לבחור מפתח גישה (לרוב באמצעות תיבת דו-שיח של מילוי אוטומטי שמופיעה כשממקדים את השדה של כניסה) ולאמת את הזהות שלו באמצעות נעילת המסך של המכשיר או באמצעות מידע ביומטרי.
  5. אחרי אימות המשתמש, ספק מפתח הגישה חותם על האתגר, והדפדפן מחזיר את פרטי הכניסה של המפתח הציבורי שנוצר (כולל החתימה) לחזית.
  6. חזית המערכת שולחת את פרטי הכניסה האלה לקצה העורפי.
  7. קצה העורפי מאמת את החתימה של פרטי הכניסה מול המפתח הציבורי המאוחסן של המשתמש. אם האימות מסתיים בהצלחה, הקצה העורפי מבצע כניסה של המשתמש לחשבון.

אימות באמצעות מפתח גישה באמצעות מילוי אוטומטי של טפסים

כדי להתחיל אימות באמצעות מפתח גישה באמצעות מילוי אוטומטי של טפסים, צריך לבצע קריאה מותנית של WebAuthn get כשדף הכניסה נטען. הקריאה ל-navigator.credentials.get() כוללת את האפשרות mediation: 'conditional'.
בקשה מותנית ל-navigator.credentials.get() API של WebAuthn לא מציגה ממשק משתמש באופן מיידי. במקום זאת, הוא ממתין בסטטוס 'בהמתנה' עד שהמשתמש יוצר אינטראקציה עם ההנחיה למילוי אוטומטי בשדה שם המשתמש. אם המשתמש בוחר מפתח גישה, הדפדפן פותר את ההבטחה בהמתנה באמצעות פרטי כניסה כדי להכניס את המשתמש לחשבון, ועוקף את שליחת הטופס המסורתית. אם המשתמש יבחר סיסמה במקום זאת, ההתחייבות לא תיפתר והתהליך הרגיל של כניסה באמצעות סיסמה ימשיך. אחרי זה, האחריות של הדף היא להכניס את המשתמש.

הוספת הערה לשדה להזנת קלט בטופס

כדי להפעיל מילוי אוטומטי של מפתח גישה, מוסיפים את המאפיין autocomplete לשדה input של שם המשתמש בטופס. צריך לכלול את הערכים username ו-webauthn כערכים מופרדים בפסיקים.

<input type="text" name="username" autocomplete="username webauthn" autofocus>

הוספת autofocus לשדה הזה תפעיל באופן אוטומטי את ההנחיה למילוי אוטומטי בזמן טעינת הדף, ותציג מיד את הסיסמאות ומפתחות הגישה הזמינים.

זיהוי תכונות

לפני שמפעילים קריאה מותנית ל-API של WebAuthn, צריך לבדוק אם:

  • הדפדפן תומך ב-WebAuthn באמצעות PublicKeyCredential.

Browser Support

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Source

Browser Support

  • Chrome: 108.
  • Edge: 108.
  • Firefox: 119.
  • Safari: 16.

Source

קטע הקוד הבא מראה איך אפשר לבדוק אם הדפדפן תומך בתכונות האלה:

// Availability of window.PublicKeyCredential means WebAuthn is usable.  
if (window.PublicKeyCredential &&  
    PublicKeyCredential.isConditionalMediationAvailable) {  
  // Check if conditional mediation is available.  
  const isCMA = await PublicKeyCredential.isConditionalMediationAvailable();  
  if (isCMA) {  
    // Call WebAuthn authentication  
  }  
}  

אחזור מידע מהקצה העורפי

הקצה העורפי צריך לספק כמה אפשרויות לקצה הקדמי כדי להתחיל את הקריאה ל-navigator.credentials.get(). בדרך כלל, האפשרויות האלה מאוחזרות כאובייקט JSON מנקודת קצה (endpoint) בשרת.

מאפייני המפתח באובייקט האפשרויות כוללים:

  • challenge: קריאה לאימות שנוצרה על ידי השרת ב-ArrayBuffer (בדרך כלל בקידוד Base64URL להעברת JSON). זה חיוני כדי למנוע התקפות שליחה מחדש. השרת צריך ליצור אתגר חדש לכל ניסיון כניסה, ולבטל אותו לאחר זמן קצר או אם הניסיון נכשל.
  • allowCredentials: מערך של תיאורי פרטי כניסה. מעבירים מערך ריק. הפקודה הזו תגרום לדפדפן להציג את כל פרטי הכניסה של rpId שצוין.
  • userVerification: האפשרות הזו קובעת את ההעדפה שלכם בנוגע לאימות המשתמשים, למשל, דרישה לנעילת מסך במכשיר. ערך ברירת המחדל והערך המומלץ הוא "preferred". הערכים האפשריים הם:

    • "required": אימות המשתמש חייב להתבצע על ידי מאמת החשבונות (למשל קוד אימות או נתונים ביומטריים). הפעולה נכשלת אם לא ניתן לבצע את האימות.
    • "preferred": האימות מנסה לאמת את המשתמש, אבל הפעולה יכולה להצליח גם בלי זה.
    • "discouraged": אם אפשר, המאמת צריך להימנע מאימות משתמשים.
  • rpId: מזהה הצד הנסמך, בדרך כלל הדומיין של האתר שלכם (למשל example.com). הערך הזה חייב להתאים בדיוק ל-rp.id ששימש ליצירת פרטי הכניסה למפתח הגישה.

השרת צריך ליצור את אובייקט האפשרויות הזה. כדי להעביר את ה-JSON, הערכים של ArrayBuffer (כמו challenge) חייבים להיות מקודדים ב-Base64URL. בחזית, אחרי ניתוח ה-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

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

אימות החתימה

כששרת הקצה מקבל את פרטי הכניסה של המפתח הציבורי, הוא צריך לאמת את האותנטיות שלו. הבדיקה כוללת:

  1. ניתוח נתוני פרטי הכניסה.
  2. חיפוש המפתח הציבורי המאוחסן שמשויך ל-id של פרטי הכניסה.
  3. אימות ה-signature שהתקבל מול המפתח הציבורי שנשמר.
  4. אימות נתונים אחרים, כמו האתגר והמקור.

מומלץ להשתמש בספריית 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.

לא לבקש גורם אימות נוסף

מפתחות גישה מספקים הגנה מובנית חזקה מפני איומים נפוצים כמו פישינג. לכן, גורם אימות שני לא מוסיף ערך אבטחה משמעותי. במקום זאת, היא יוצרת שלב מיותר למשתמשים במהלך הכניסה.

רשימת המשימות

  • לאפשר למשתמשים להיכנס באמצעות מפתח גישה דרך מילוי אוטומטי של טפסים.
  • אות שמופיע כשפרטי הכניסה התואמים של מפתח הגישה לא נמצאים בקצה העורפי.
  • להציג למשתמש בקשה ליצור מפתח גישה באופן ידני אם הוא עדיין לא יצר מפתח כזה אחרי הכניסה לחשבון.
  • יצירה אוטומטית של מפתח גישה (יצירה מותנית) אחרי שהמשתמש נכנס לחשבון באמצעות סיסמה (וגורם אימות שני).
  • להציג בקשה ליצירת מפתח גישה מקומי אם המשתמש נכנס לחשבון באמצעות מפתח גישה במכשירים שונים.
  • לשלוח לספקי את רשימת מפתחות הגישה הזמינים ואת פרטי המשתמש המעודכנים (שם המשתמש, שם המוצג) אחרי הכניסה או כשמתרחשים שינויים.

משאבים