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

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

סיכום

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

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

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