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

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

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

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

איך זה עובד

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

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

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

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

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

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

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

תאימות

רוב הדפדפנים תומכים ב-WebAuthn, אבל יש פערים קטנים. במאמר Device Support - passkeys.dev מפורט אילו שילובים של דפדפנים ומערכות הפעלה תומכים ביצירת מפתח גישה.

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

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

זיהוי תכונות

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

  • הדפדפן תומך ב-WebAuthn עם PublicKeyCredential.

תמיכה בדפדפנים

  • Chrome:‏ 67.
  • Edge:‏ 18.
  • Firefox:‏ 60.
  • Safari: 13.

מקור

  • המכשיר תומך בכלי לאימות פלטפורמה (שיכול ליצור מפתח גישה ולבצע אימות באמצעות מפתח הגישה) באמצעות PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().

תמיכה בדפדפנים

  • Chrome: 67.
  • קצה: 18.
  • Firefox: 60.
  • Safari: 13.

מקור

תמיכה בדפדפנים

  • Chrome:‏ 108.
  • Edge:‏ 108.
  • Firefox:‏ 119.
  • Safari: 16.

מקור

// 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 מחזיר הבטחה וממתין לאינטראקציה של המשתמש כדי להציג תיבת דו-שיח מודלית.

תמיכה בדפדפנים

  • Chrome: 60.
  • Edge:‏ 18.
  • Firefox:‏ 60.
  • Safari: 13.

מקור

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 הוא דומיין, ואתר יכול לציין את הדומיין שלו או סיומת שניתן לרשום. לדוגמה, אם המקור של הגורם המוגבל הוא https://login.example.com:1337, מזהה הגורם המוגבל (RP) יכול להיות login.example.com או example.com. אם מזהה RP מצוין בתור example.com, המשתמש יכול לבצע אימות ב-login.example.com או בכל תת-דומיין של example.com.

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

  • pubKeyCredParams: השדה הזה מציין מהם האלגוריתמים הנתמכים של מפתח ציבורי של הגורם המוגבל. מומלץ להגדיר אותו כ-[{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: אובייקט אימות בקידוד ArrayBuffer. הוא מכיל מידע חשוב כמו מזהה RP, דגלים ומפתח ציבורי.
  • authenticatorAttachment: הפונקציה מחזירה את הערך "platform" כשפרטי הכניסה האלה נוצרים במכשיר שתומך במפתחות גישה.
  • type: השדה הזה תמיד מוגדר ל-"public-key".

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

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

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

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

  • מזהה פרטי הכניסה (מפתח ראשי)
  • User ID
  • מפתח ציבורי

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

הוראות מפורטות יותר זמינות במאמר רישום מפתח גישה בצד השרת

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

משאבים