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

המטרה של פרטי כניסה מסוג 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

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

סיכום

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

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

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