ثبت گذرکلید

این راهنما جزئیات نحوه فعال کردن کاربران برای ثبت گذرکلید برای حسابشان را ارائه می‌دهد و یک جایگزین ورود به سیستم بدون گذرواژه بسیار امن و مقاوم دربرابر رمزگیری ارائه می‌دهد.

الزامات پایگاه داده

برای پشتیبانی از ثبت گذرکلید، جدول اطلاعات کاربری پایگاه داده شما باید فیلدهای زیر را ذخیره کند:

export interface StoredPasskeyCredential {
  id: string; // Base64URL-encoded credential ID (unique lookup key)
  passkeyUserId: string; // Associated application user ID
  credentialPublicKey: string; // Base64URL-encoded public key used to verify assertion signatures
  credentialType: "public-key";
  credentialDeviceType: "singleDevice" | "multiDevice"; // Helps distinguish device-bound vs cloud-synced passkeys
  credentialBackedUp: boolean; // Boolean backup state reported by the authenticator
  aaguid: string; // Authenticator Attestation GUID
  providerIcon?: string; // Provider icon derived from the AAGUID registry (dark or light theme URLs)
  name: string; // Provider name derived from AAGUID registry
  transports: string[]; // Array of transport names (e.g. 'internal', 'hybrid') necessary for exclusion options
  lastUsedAt?: number; // Optional epoch timestamp of last sign-in
  registeredAt: number; // Registration epoch timestamp
  counter: number; // Authenticator sign-in signature counter used to prevent replay attacks
}

سمت سرور

تولید کردن گزینه‌ها

نقطه پایانی ایجاد کنید که پارامترهای ایجاد WebAuthn را تولید کند. به‌جای رمزنگاری دستی، به کتابخانه بررسی‌شده براساس استانداردهای دسته‌بندی تکیه کنید.

  1. استفاده از شناسه RP ازپیش‌تعریف‌شده: از شناسه RP مناسب ازپیش‌تعریف‌شده به‌عنوان رشته ثابت استفاده کنید.
  2. ایجاد «چالش» ایمن: یک میان‌گیر تصادفی با آنتروپی بالا و ازنظر رمزنگاری ایمن در سرور ایجاد کنید، آن را به‌طور ایمن در جلسه کاربر ذخیره کنید، و آن را به‌عنوان Base64URL برای ارائه گزینه‌ها کدگذاری کنید.
  3. اجتناب از گذرکلیدهای تکراری: شناسه‌های اطلاعات اعتباری پیش‌ثبت‌شده موجود کاربر را به آرایه گزینه‌های excludeCredentials نگاشت کنید. این کار از ثبت اطلاعات کاربری تکراری توسط اصالت‌سنج در همان حساب ارائه‌دهنده گذرکلید جلوگیری می‌کند.
  4. اجرای اعتبارنامه‌های قابل‌کشف: requireResidentKey: true و residentKey: "required" را در گزینه‌های authenticatorSelection تنظیم کنید تا اعتبارنامه قابل‌کشف درخواست شود، که برای ورودهای قابل‌کشف ضروری است.
  5. پیکربندی درستی‌سنجی کاربر: userVerification: "preferred" یا userVerification: "required" را مشخص کنید. بسیاری از موارد استفاده از رعایت مقررات (مثلاً امور مالی، مراقبت‌های بهداشتی) به 'required' نیاز دارند تا درستی‌سنجی کاربر را هنگام ایجاد اعمال کنند.
  6. تعیین دامنه پیوست:
    • جریان تبلیغ: هنگام پیشنهاد ایجاد گذرکلید بلافاصله پس‌از ورود به سیستم با گذرواژه استاندارد یا تبلیغات پس‌از ثبت‌نام، authenticatorAttachment: "platform" را تنظیم کنید تا اصالت‌سنج پلاتفرم را اعمال کند و درخواست‌های کلید امنیتی خارجی را دور بزند.
    • جریان مدیریت: وقتی از پانل تنظیمات یا امنیت اختصاصی که در آن کلیدهای امنیتی خارجی علاوه‌بر اصالت‌سنج پلاتفرم پشتیبانی می‌شود فراخوانی می‌شود، ویژگی authenticatorAttachment را کاملاً حذف کنید.
    • نکته: پرچم درخواست promotion: boolean را بپذیرید تا هر دو جریان را به‌طور مشروط با یک نقطه پایانی مدیریت کنید.
// Options generation example
const options = {
  challenge: serverGeneratedBase64UrlChallenge, // Cryptographically random challenge
  rp: { id: "example.com", name: "Secure Application" },
  user: {
    id: userBase64UrlId, // Unique base64url string identifying the account
    name: "user@example.com",
    displayName: "Jane Doe",
  },
  pubKeyCredParams: [
    {
      type: "public-key",
      alg: -7,
    },
    {
      type: "public-key",
      alg: -257,
    },
  ],
  excludeCredentials: userExistingCredentials.map((cred) => ({
    type: "public-key",
    id: cred.id,
    transports: cred.transports,
  })),
  authenticatorSelection: {
    residentKey: "required",
    requireResidentKey: true,
    userVerification: "preferred",
    ...(isPromotionFlow && { authenticatorAttachment: "platform" }),
  },
};

درستی‌سنجی

  1. درستی‌سنجی چالش: چالش را به‌طور ایمن دربرابر چالش محدود به جلسه موردانتظار درستی‌سنجی کنید.
  2. درستی‌سنجی حضور کاربر:
    • مطمئن شوید پرچم User Present (UP)‎ که در داده‌های تجزیه‌شده اصالت‌سنج برگردانده شده است true باشد تا حضور فیزیکی کاربر در زمان ایجاد تأیید شود.
  3. Relaxing Verification for 'preferred':
    • وقتی گزینه‌های ایجاد userVerification: "preferred" را مشخص کرد، تماس درستی‌سنجی سمت سرور باید با requireUserVerification: false پیکربندی شود. درغیراین‌صورت، اصالت‌سنج‌هایی که بدون درستی‌سنجی کاربر ثبت می‌شوند (مثلاً قفل‌های صفحه‌نمایش غیرفعال‌شده) باعث ایجاد خطاهای درستی‌سنجی سرور کاذب می‌شوند.

منطق سمت کارخواه

  1. دروازه واسط کاربر در بارگیری صفحه:
    • هنگام بار شدن صفحه، PublicKeyCredential.getClientCapabilities() را فراخوانی کنید و اگر conditionalGet یا passkeyPlatformAuthenticator دردسترس نیست، دکمه «ایجاد گذرکلید» را غیرفعال کنید.
  2. فراخوانی ایجاد و سریال‌سازی: گزینه‌های سرور را با PublicKeyCredential.parseCreationOptionsFromJSON() رمزگشایی کنید و پیکربندی حاصل را به navigator.credentials.create() ارسال کنید.
    • قبل‌از واکشی نقطه پایانی درستی‌سنجی، credential.toJSON() را فراخوانی کنید تا AuthenticatorAttestationResponse را در یک شیء معتبر و قابل‌سریال‌سازی JSON کدبندی کند.
  3. رسیدگی به استثناهای WebAuthn:
    • InvalidStateError: گذرکلید منطبقی ازقبل وجود دارد (منطبق با excludeCredentials).
    • NotAllowedError: کاربر کادر گفتگوی گذرکلید اصالت‌سنجی را لغو کرد یا زمان آن به‌پایان رسید.
    • AbortError: عملیات لغو شده است.
    • SecurityError: مبدأهای امن (HTTPS) یا خطاهای عدم تطابق شناسه RP (مشکلات پیکربندی).
  4. Try/Catch Segregation for Signal API:
    • Wrap server verification fetch() call in a try/catch block. وقتی واکشی درستی‌سنجی سرور ناموفق بود (هر وضعیت response.ok === false یا خطای شبکه)، signalUnknownCredential() را فراخوانی کنید.
// optionsFetch and registerVerifyFetch are app-defined HTTP methods
import { optionsFetch, registerVerifyFetch } from "./api.js";

async function registerPasskey(isPromotion = false) {
  // Verify passkey capability and conditional UI are available
  const capabilities = await PublicKeyCredential.getClientCapabilities();
  if (
    !capabilities.passkeyPlatformAuthenticator ||
    !capabilities.conditionalGet
  ) {
    // Hide "Create passkey" buttons and fall back to password flows instead
    showStandardPasswordFallbackUI();
    return;
  }

  const creationOptionsJSON = await optionsFetch({ promotion: isPromotion });
  const publicKey =
    PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);

  let credential;
  try {
    // passkey prompt execution
    credential = await navigator.credentials.create({ publicKey });
  } catch (err) {
    if (err.name === "InvalidStateError") {
      console.log("A passkey already exists for this account.");
      alert("A passkey already exists for this account.");
    } else if (err.name === "SecurityError") {
      console.error("Configuration RP ID or Secure Context error.");
      alert("Configuration RP ID or Secure Context error.");
    } else if (err.name === "NotAllowedError") {
      console.log("User cancelled the passkey dialog.");
    } else if (err.name === "AbortError") {
      console.log("The creation operation has been aborted.");
    }
    return; // Safe API exit, do not signal unknown for standard WebAuthn cancels
  }

  // Server Verification phase (Segregated Try/Catch)
  let encodedResponse = credential.toJSON();
  try {
    const response = await registerVerifyFetch(encodedResponse);
    if (!response.ok) {
      // Server verification failed to verify/authenticate the credential (orphaned)
      await PublicKeyCredential.signalUnknownCredential({
        rpId, // RP ID must match the one defined on the server
        credentialId: encodedResponse.id, // Base64URL-encoded credential ID
      });
    }
  } catch (serverErr) {
    console.error("Server verification network failure:", serverErr);
    await publickeycredential.signalunknowncredential({
      rpId, // RP ID must match the one defined on the server
      credentialid: encodedresponse.id, // base64url-encoded credential id
    });
  }
}

راهبردهای جایگزین

جایگزین همگام‌سازی Signal API

WebAuthn Signal API‏ (webauthn-signals) یک بهینه‌سازی پیش‌رونده است که برای همگام نگه داشتن مدیران گذرواژه با وضعیت اعتبارنامه سرور استفاده می‌شود.

  • تجربه بازگشت: اگر PublicKeyCredential.signalUnknownCredential توسط مرورگر پشتیبانی نشود، تماس باید به‌طور ایمن ازطریق دروازه تشخیص ویژگی (if (PublicKeyCredential.signalUnknownCredential)) دور زده شود و درستی‌سنجی سمت سرور به‌سادگی شکست را بدون راه‌اندازی به‌روزرسانی‌های مدیر ثبت می‌کند.

Easy JSON Serialization Fallback

همیشه «webauthn-polyfills» را نصب کنید و آن را در بافت وارد کنید. درنظر بگیرید که تا زمانی که PublicKeyCredential پشتیبانی می‌شود، PublicKeyCredential.parseCreationOptionsFromJSON و PublicKeyCredential.prototype.toJSON نیز پشتیبانی می‌شوند.

import 'webauthn-polyfills';