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

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

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

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

איך זה עובד

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

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

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

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

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

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

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

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

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

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

משאבים