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

מטרת פרטי הכניסה של 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 ברמה 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() ולסמן את שדה הקלט username ב-HTML באמצעות 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.

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

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

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

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