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

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

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

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

איך זה עובד

אפשר לבקש מהמשתמשים ליצור מפתח גישה באחד מהמצבים הבאים:

  • כשמשתמש נכנס באמצעות סיסמה.
  • כשמשתמש נכנס באמצעות מפתח גישה ממכשיר אחר (כלומר, authenticatorAttachment הוא cross-platform).
  • בדף ייעודי שבו המשתמשים יכולים לנהל את מפתחות הגישה שלהם.

כדי ליצור מפתח גישה צריך להשתמש ב-WebAuthn API.

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

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

התהליך להוספת מפתח גישה חדש לחשבון משתמש קיים:

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

תאימות

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

יצירת מפתח גישה חדש

כך ממשק קצה צריך לפעול כשמתקבלת בקשה ליצירת מפתח גישה חדש.

זיהוי תכונות

לפני הצגת הלחצן 'יצירת מפתח גישה חדש', צריך לבדוק אם:

  • הדפדפן תומך ב-WebAuthn.
  • המכשיר תומך באימות פלטפורמה (אפשר ליצור מפתח גישה ולבצע אימות באמצעות מפתח הגישה).
  • הדפדפן תומך בממשק משתמש מותנה של WebAuthn.
// Availability of `window.PublicKeyCredential` means WebAuthn is usable.  
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.  
// `​​isConditionalMediationAvailable` means the feature detection is usable.  
if (window.PublicKeyCredential &&  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&  
    PublicKeyCredential.​​isConditionalMediationAvailable) {  
  // Check if user verifying platform authenticator is available.  
  Promise.all([  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),  
    PublicKeyCredential.​​isConditionalMediationAvailable(),  
  ]).then(results => {  
    if (results.every(r => r === true)) {  
      // Display "Create a new passkey" button  
    }  
  });  
}  

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

אחזור מידע חשוב מהקצה העורפי

כשהמשתמש לוחץ על הלחצן, מאחזרים מידע חשוב כדי להתקשר אל navigator.credentials.create() מהקצה העורפי:

  • challenge: אתגר שנוצר על ידי שרת ב-ArrayBuffer עבור הרישום הזה. זו חובה, אבל לא משתמשים בה במהלך ההרשמה, אלא אם מבצעים אימות – נושא מתקדם שלא מופיע כאן.
  • user.id: המזהה הייחודי של המשתמש. הערך הזה חייב להיות ArrayBuffer שלא כולל פרטים אישיים מזהים, לדוגמה, כתובות אימייל או שמות משתמשים. ערך אקראי של 16 בייטים שנוצר לכל חשבון יפעל היטב.
  • user.name: בשדה הזה צריך להיות מזהה ייחודי של החשבון שהמשתמש יזהה, כמו כתובת האימייל או שם המשתמש. השם הזה יוצג בבורר החשבונות. (אם משתמשים בשם משתמש, צריך להשתמש באותו ערך שמופיע באימות הסיסמה).
  • user.displayName: השדה הזה הוא שם לחשבון שידידותי יותר למשתמש. הוא לא חייב להיות ייחודי, והוא יכול להיות השם שהמשתמש בחר. אם לאתר שלך אין ערך מתאים להכללה כאן, העבירו מחרוזת ריקה. ייתכן שהוא יוצג בבורר החשבונות, בהתאם לדפדפן.
  • excludeCredentials: כדי למנוע רישום של אותו מכשיר, צריך לספק רשימה של מזהים של פרטי כניסה שכבר רשומים. אם מציינים את מנוי transports, הוא צריך לכלול את התוצאה של הקריאה ל-getTransports() במהלך הרישום של כל פרטי כניסה.

קריאה ל-WebAuthn API כדי ליצור מפתח גישה

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

const publicKeyCredentialCreationOptions = {
  challenge: *****,
  rp: {
    name: "Example",
    id: "example.com",
  },
  user: {
    id: *****,
    name: "john78",
    displayName: "John",
  },
  pubKeyCredParams: [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}],
  excludeCredentials: [{
    id: *****,
    type: 'public-key',
    transports: ['internal'],
  }],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    requireResidentKey: true,
  }
};

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

// Encode and send the credential to the server for verification.  

הפרמטרים שלא מוסברים למעלה הם:

  • rp.id: מזהה RP הוא דומיין, ואתר יכול לציין את הדומיין שלו או סיומת שניתן לרשום. לדוגמה, אם המקור של הגורם המוגבל (RP) הוא https://login.example.com:1337, מזהה הגורם המוגבל (RP) יכול להיות login.example.com או example.com. אם מזהה הגורם המוגבל (RP) מצוין כ-example.com, המשתמש יכול לבצע אימות ב-login.example.com או בכל תת-דומיינים ב-example.com.

  • rp.name: השם של הגורם המוגבל.

  • pubKeyCredParams: השדה הזה מציין את האלגוריתמים הנתמכים של מפתח ציבורי ב-RP. מומלץ להגדיר אותו לערך [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]. המדיניות הזו כוללת תמיכה ב-ECDSA עם P-256 וב-RSA PKCS#1, ותמיכה בהם מספקת כיסוי מלא.

  • authenticatorSelection.authenticatorAttachment: יש להגדיר את הערך "platform" אם יצירת מפתח הגישה הזו היא שדרוג מסיסמה, למשל במבצע לאחר כניסה. "platform" מציין שה-RP רוצה מאמת פלטפורמה (מאמת חשבונות שמוטמע במכשיר בפלטפורמה) שלא יציג בקשה להכניס, למשל מפתח אבטחה בחיבור USB. למשתמשים יש אפשרות פשוטה יותר ליצור מפתח גישה.

  • authenticatorSelection.requireResidentKey: צריך להגדיר את הערך כ-"true" בוליאני. פרטי כניסה שגלויים לכולם (מפתח תושב) מאחסנים את פרטי המשתמשים במפתח הגישה ומאפשרים למשתמשים לבחור את החשבון בזמן האימות.

  • authenticatorSelection.userVerification: מציין אם אימות משתמש באמצעות נעילת המסך של המכשיר הוא "required", "preferred" או "discouraged". ברירת המחדל היא "preferred", והמשמעות היא שמאמת החשבונות עשוי לדלג על אימות המשתמש. צריך להגדיר את הערך כ-"preferred" או להשמיט את המאפיין.

שליחת פרטי הכניסה של המפתח הציבורי שהוחזרו לקצה העורפי

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

יש סיבות שונות לדחיית ההבטחה. אפשר לטפל בשגיאות האלה על ידי בדיקת המאפיין name של האובייקט Error:

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

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

  • id: מזהה בקידוד Base64URL של מפתח הגישה שנוצר. המזהה הזה עוזר לדפדפן לקבוע אם מפתח גישה תואם נמצא במכשיר בזמן האימות. יש לאחסן את הערך הזה במסד הנתונים בקצה העורפי.
  • rawId: גרסת ArrayBuffer של מזהה פרטי הכניסה.
  • response.clientDataJSON: נתוני לקוח בקידוד ArrayBuffer.
  • response.attestationObject: אובייקט אימות (attestation) בקידוד ArrayBuffer. הוא מכיל מידע חשוב כמו מזהה RP, דגלים ומפתח ציבורי.
  • authenticatorAttachment: מוחזר "platform" כשפרטי הכניסה האלה נוצרים במכשיר שתומך במפתח גישה.
  • type: השדה הזה תמיד מוגדר ל-"public-key".

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

שמירת פרטי הכניסה

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

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

  • Credential ID (Primary key)
  • User ID
  • מפתח ציבורי

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

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

משאבים