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

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

הרגע הנכון להפעלת המודעה

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

  • אל תנסו ליצור תהליכים מותנים ללא סיסמה (למשל, קישורים חד-פעמיים לכניסה, סיסמאות חד-פעמיות ב-SMS או איחוד זהויות).
  • אם נדרש אימות רב-שלבי, צריך לחכות עד שכל השלבים יושלמו בהצלחה לפני שמתחילים ליצור את התנאי.
  • לפני ששולחים בקשות לנקודות קצה ליצירה, צריך לוודא שסשן המשתמש המאומת תקין ופעיל.

שלבי ההטמעה

1. ביטול פעולות קודמות של מילוי אוטומטי

אם דף הכניסה משתמש במילוי אוטומטי של טפסים (ממשק משתמש מותנה/Get), צריך לבטל את הקריאה הפעילה לקבלת פרטי הכניסה כדי למנוע התנגשויות בדפדפן.

  • צריך להתקשר אל abortController.abort() ב-AbortController שמצורף לבקשת המילוי האוטומטי של navigator.credentials.get() בהמתנה לפני שמתקשרים אל navigator.credentials.create().

2. זיהוי תכונות

כדי לבדוק אם אפשר להשתמש ביצירה מותנית, בודקים את conditionalCreate באמצעות PublicKeyCredential.getClientCapabilities().

const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalCreate) {
  // Conditional create is available
}

3. יצירת מפתח גישה באמצעות Conditional Create

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

4. טיפול בשגיאות שקטות

  • עוטפים את ההנחיה ליצירת מפתח הגישה (navigator.credentials.create) בבלוק try/catch. חובה לתפוס חריגות אופייניות שגלויות למשתמשים (InvalidStateError,‏ NotAllowedError,‏ AbortError) ולהתעלם מהן בלי להציג למשתמש ממשק משתמש של שגיאה.

5. אימות נוכחות משתמש בצד השרת

  • נקודת הקצה לאימות בצד השרת חייבת להקל על הדרישה לנוכחות משתמש (UP) (requireUserPresence: false) רק כשמאמתים פרטי כניסה שנוצרו על ידי טריגר ליצירה מותנית. אימות נוכחות קפדני חייב להישאר פעיל ביצירות רגילות של תוכן בוטה.

6. טיפול באימות שרת שנכשל

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

קוד לדוגמה

// optionsFetch and registerVerifyFetch are app-defined server endpoint requests
import { optionsFetch, registerVerifyFetch } from './api.js';

async function triggerConditionalCreate(loginAbortController) {
  const capabilities = await PublicKeyCredential.getClientCapabilities();
  if (capabilities.conditionalCreate !== true) {
    return; // Platform does not support conditional creation
  }

  // 1. Abort any active autofill conditional-get controllers to clear the WebAuthn pipeline
  loginAbortController.abort();

  // 2. Fetch creation options signaling the backend that this is a conditional request
  const creationOptionsJSON = await optionsFetch({ conditional: true });
  const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);

  let credential;
  try {
    // 3. Invoke silent credentials creation prompt
    credential = await navigator.credentials.create({ 
      publicKey,
      mediation: 'conditional' // Silent background creation mediation
    });
  } catch (e) {
    // 4. Silently swallow common WebAuthn browser exceptions
    if (['InvalidStateError', 'NotAllowedError', 'AbortError'].includes(e.name)) {
      return; 
    }
    console.error('Unexpected conditional create error:', e);
    return;
  }

  // 5. Server verification step using dedicated Try/Catch block
  let encodedResponse = credential.toJSON();
  try {
    const response = await registerVerifyFetch(encodedResponse);
    if (!response.ok) {
      // If the server verification fails, clean up using Signal API
      if (PublicKeyCredential.signalUnknownCredential) {
        await PublicKeyCredential.signalUnknownCredential({
          rpId, // RP ID must match the one defined on the server
          credentialId: encodedResponse.id
        });
      }
    }
  } catch (serverErr) {
    console.error('Verification network failure:', serverErr);
    if (PublicKeyCredential.signalUnknownCredential) {
      await PublicKeyCredential.signalUnknownCredential({
        rpId, // RP ID must match the one defined on the server
        credentialId: encodedResponse.id
      });
    }
  }
}

שיטות חלופיות

זיהוי תכונות של מפתחות גישה – חלופה

תמיד צריך להתקין את webauthn-polyfills ולייבא אותו בהקשר. כל עוד יש תמיכה ב-PublicKeyCredential, יש תמיכה גם ב-PublicKeyCredential.getClientCapabilities.

import 'webauthn-polyfills';

חלופה לסנכרון של Signal API

‫WebAuthn Signal API‏ (webauthn-signals) הוא אופטימיזציה מתקדמת שמשמשת לסנכרון של מנהלי הסיסמאות עם מצב פרטי הכניסה בשרת.

  • תהליך לגיבוי: מוגבל באמצעות if (PublicKeyCredential.signalUnknownCredential). אם אין תמיכה, המערכת תעקוף את סנכרון האימות ברקע בלי להציג חריגות בדפדפן.

Easy JSON Serialization Fallback

תמיד צריך להתקין את webauthn-polyfills ולייבא אותו בהקשר. כל עוד יש תמיכה ב-PublicKeyCredential, יש תמיכה גם ב-PublicKeyCredential.parseCreationOptionsFromJSON וב-PublicKeyCredential.prototype.toJSON.

import 'webauthn-polyfills';