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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

זיהוי תכונות

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

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

Browser Support

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

Source

Browser Support

  • Chrome: 133.
  • Edge: 133.
  • Firefox: 135.
  • Safari: 17.4.

Source

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

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: מערך Buffer של נתוני אמצעי אימות. השדה הזה כולל מידע כמו מזהה ספק הזהויות.
  • 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
});

אימות החתימה

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

  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.

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

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

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

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

משאבים