ניתוח מעמיק של פרטי כניסה שגלויים

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

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

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

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

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

requireResidentKey וגם residentKey

כדי ליצור פרטי כניסה שגלויים לכולם, צריך לציין את authenticatorSelection.residentKey ואת authenticatorSelection.requireResidentKey ב-navigator.credentials.create() עם הערכים שמפורטים בהמשך.

async function register () {
  // ...

  const publicKeyCredentialCreationOptions = {
    // ...
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      residentKey: 'required',
      requireResidentKey: true,
    }
  };

  const credential = await navigator.credentials.create({
    publicKey: publicKeyCredentialCreationOptions
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;

  // ...
}

residentKey:

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

requireResidentKey:

  • המאפיין הזה נשמר לצורך תאימות לאחור מ-WebAuthn Level 1, גרסה ישנה יותר של המפרט. יש להגדיר את הערך הזה לערך true אם הערך של residentKey הוא 'required'. אחרת, יש להגדיר אותו לערך false.

allowCredentials

גורמים מוגבלים יכולים להשתמש בallowCredentials ב-navigator.credentials.get() כדי לשלוט בחוויית האימות של מפתחות הגישה. בדרך כלל יש שלושה סוגים של אפשרויות לאימות מפתחות גישה:

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

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

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // You can omit `allowCredentials` as well:
    allowCredentials: []
  };

  const credential = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}

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

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

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

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

כדי להשיג את חוויית המשתמש הזו, בנוסף להעברת מערך ריק למאפיין allowCredentials או להשמטת הפרמטר, צריך לציין mediation: 'conditional' ב-navigator.credentials.get() ולהוסיף הערה לשדה קלט ה-HTML username עם autocomplete="username webauthn" או לשדה להזנת קלט password עם autocomplete="password webauthn".

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

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // You can omit `allowCredentials` as well:
    allowCredentials: []
  };

  const cred = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal,
    // Specify 'conditional' to activate conditional UI
    mediation: 'conditional'
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}
<input type="text" name="username" autocomplete="username webauthn" ...>

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

אימות מחדש

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

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

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

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // Provide a list of PublicKeyCredentialDescriptors:
    allowCredentials: [{
      id: ****,
      type: 'public-key',
      transports: [
        'internal',
        'hybrid'
      ]
    }, {
      id: ****,
      type: 'public-key',
      transports: [
        'internal',
        'hybrid'
      ]
    }, ...]
  };

  const credential = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}

אובייקט PublicKeyCredentialDescriptor מורכב מהרכיבים הבאים:

  • id: מזהה של פרטי הכניסה למפתח הציבורי שה-RP השיג ברישום של מפתח הגישה.
  • type: השדה הזה הוא בדרך כלל 'public-key'.
  • transports: רמז להעברות שנתמכות על ידי המכשיר שמכיל את פרטי הכניסה האלה. הרמז הזה משמש את הדפדפנים לביצוע אופטימיזציה של ממשק המשתמש שמבקש מהמשתמש להציג מכשיר חיצוני. אם מציינים את הרשימה הזו, היא צריכה לכלול את התוצאה של הקריאה ל-getTransports() במהלך הרישום של כל פרטי כניסה.

סיכום

פרטי כניסה שגלויים לכולם הופכים את הכניסה עם מפתח גישה לידידותית יותר למשתמש, כי הם מאפשרים להם לדלג על הזנת שם משתמש. בעזרת השילוב של residentKey, requireResidentKey ו-allowCredentials, גורמים מוגבלים יכולים להשיג חוויית כניסה ש:

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

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