مدیریت گذرکلید

این راهنما جزئیات مربوط به نحوه فعال کردن کاربران برای مشاهده، تغییر نام، و حذف گذرکلیدهای ثبت‌شده‌شان را ارائه می‌دهد، درحالی‌که اطلاعات اعتباری ذخیره‌شده و جزئیات نمایه کاربر را بااستفاده از Signal API بین سرور و مدیران گذرواژه کاربر همگام‌سازی می‌کند.

عملیات سمت سرور

لایه پایگاه داده و نقاط پایانی باطن شما باید از کنش‌های CRUD رایج برای اطلاعات کاربری ثبت‌شده و همچنین به‌روزرسانی جزئیات نمایه کاربر پشتیبانی کند. سرور که از کتابخانه‌های مختص چارچوب جدا شده است، نقطه‌های پایانی را برای موارد زیر نمایش می‌دهد:

  1. فهرست کردن همه اطلاعات اعتباری کاربر: همه سوابق StoredPasskeyCredential را که با شناسه کاربر واردشده به سیستم مطابقت دارد واکشی کنید.
  2. به‌روزرسانی نام‌های اطلاعات کاربری: پذیرفتن نام مستعار سفارشی جدید برای شناسه اطلاعات کاربری خاص و حفظ به‌روزرسانی در سرور.
  3. حذف اطلاعات اعتباری: شناسه اطلاعات اعتباری خاصی را از پایگاه داده بردارید.
  4. به‌روزرسانی جزئیات حساب کاربری: مقادیر به‌روزشده name (نام کاربری) و displayName را برای نمایه کاربر واردشده به سیستم بپذیرید تا کارخواه بتواند آن‌ها را با ارائه‌دهنده گذرکلید همگام‌سازی کند.
// Node.js routing example for credential CRUD and user profile rename
router.get('/api/credentials', checkUserAuthenticated, async (req, res) => {
  const list = await db.findCredentialsByUserId(req.user.id);
  return res.json(list);
});

router.put('/api/credential/:id', checkUserAuthenticated, async (req, res) => {
  const { id } = req.params;
  const { name } = req.body;
  const cred = await db.findCredentialById(id);
  if (!cred || cred.passkeyUserId !== req.user.id) {
    return res.status(404).json({ error: 'Credential not found.' });
  }
  cred.name = name;
  await db.saveCredential(cred);
  return res.json(cred);
});

router.delete('/api/credential/:id', checkUserAuthenticated, async (req, res) => {
  const { id } = req.params;
  const cred = await db.findCredentialById(id);
  if (!cred || cred.passkeyUserId !== req.user.id) {
    return res.status(404).json({ error: 'Credential not found.' });
  }
  await db.deleteCredential(id);
  return res.json({ success: true });
});

router.put('/api/user', checkUserAuthenticated, async (req, res) => {
  const { name, displayName } = req.body;
  const updatedUser = await db.updateUser(req.user.id, { name, displayName });
  return res.json(updatedUser);
});

میانای کاربر مدیریت سمت کارخواه

ارائه پانل تنظیمات اختصاصی که به کاربران امکان می‌دهد گزینه‌های اصالت‌سنجی ثبت‌شده و جزئیات نمایه حساب خود را به‌راحتی بازرسی و مدیریت کنند:

  1. نمایش فهرست ذخیره‌شده: فهرست را از نقطه پایانی‌تان واکشی کنید و ردیف‌های اعتبارنامه را به‌صورت جداگانه رندر کنید. اگر پاسخ خالی است، پیام وضعیت خالی مفیدی ارائه دهید (برای مثال، «گذرکلیدی پیدا نشد»).
  2. فراداده AAGUID را نگاشت کنید: برای هر گذرکلید، ویژگی aaguid آن را در دفتر ثبت محلی‌تان جستجو کنید تا جزئیات ارائه‌دهنده آن ارائه شود. برای جزئیات بیشتر، بخش تعیین ارائه‌دهنده گذرکلید از AAGUID را ببینید.
  3. الزامات واسط کاربر برای هر مورد: هر ردیف داخل محفظه فهرست باید موارد زیر را ارائه دهد:
    • نماد ارائه‌دهنده: تصویر مشتق‌شده از AAGUID یا URI داده.
    • نام ارائه‌دهنده/سفارشی: نام مشتق‌شده از AAGUID یا رشته تغییرنام‌یافته توسط کاربر.
    • تاریخ ثبت: مُهر زمان تاریخ خام پایدار پایگاه داده registeredAt که برای نمایش به کارخواه به تاریخ قابل‌خواندن برای انسان قالب‌بندی شده است.
    • تاریخ آخرین استفاده: مُهر زمان مبدأ خام پایدار در پایگاه داده lastUsedAt که برای نمایش به مشتری در قالب تاریخ قابل‌خواندن برای انسان (درصورت وجود) درآمده است.
    • دکمه تغییر نام: کنش تغییر نام را برای به‌روزرسانی نام مستعار سفارشی اعتبارنامه در سرور (PUT /api/credential/:id) راه‌اندازی می‌کند. توجه: تغییر نام مستعار گذرکلید به‌صورت تکی یک تغییر برچسب سمت سرور است و signalCurrentUserDetails() را فراخوانی نمی‌کند.
    • دکمه حذف: حذف اعتبارنامه (DELETE /api/credential/:id) را راه‌اندازی می‌کند و شناسه‌های اعتبارنامه باقی‌مانده را ازطریق signalAllAcceptedCredentials() همگام‌سازی می‌کند.
  4. میانای کاربر تغییر نام حساب کاربری:
    • کنترلی ارائه دهید که به کاربر اجازه دهد نام کاربری حساب (name) یا displayName (PUT /api/user) خود را به‌روز کند و جزئیات به‌روزشده کاربر را فوراً ازطریق PublicKeyCredential.signalCurrentUserDetails() با مدیر گذرواژه همگام‌سازی کند.
  5. دکمه شرطی «ایجاد گذرکلید»:
    • دکمه راه‌انداز ثبت‌نام «ایجاد گذرکلید» را به‌طور برجسته در صفحه مدیریت ارائه دهید. قبل‌از ارائه این عنصر میانای کاربر، صفحه باید قابلیت‌های تشخیص ویژگی را بااستفاده از PublicKeyCredential.getClientCapabilities() برای تأیید پشتیبانی از اصالت‌سنج پلاتفرم داشته باشد. اگر گذرکلیدها پشتیبانی نمی‌شوند، این دکمه را پنهان کنید و به‌جای آن، کاربر را به‌طور مناسب به ثبت‌نام در MFA استاندارد تشویق کنید.
    • اجازه ثبت کلید امنیتی با حذف authenticatorSelection.authenticatorAttachment در تماس navigator.credentials.create().

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

Signal API به برنامه اجازه می‌دهد وضعیت‌های اعتبارنامه را به مدیران گذرواژه منتقل کند و صندوق‌های همگام‌سازی‌شده کاربر و پایگاه داده باطن شما را هم‌گام نگه دارد.

  • قانون تشخیص ویژگی و کدگذاری پارامتر:
    • همیشه قبل‌از فراخوانی روش‌های Signal API، آن‌ها را ازنظر ویژگی شناسایی کنید (if (PublicKeyCredential.signalAllAcceptedCredentials) و if (PublicKeyCredential.signalCurrentUserDetails)).
    • همه پارامترهای شناسه userId و اطلاعات کاربری که به روش‌های Signal API (signalAllAcceptedCredentials، signalCurrentUserDetails) ارسال می‌شوند باید رشته‌های کدگذاری‌شده Base64URL باشند.
  • درحال شروع همگام‌سازی بار کردن صفحه:
    • برنامه باید signalAllAcceptedCredentials() را به‌طور خودکار هنگام بار شدن صفحه مدیریت فراخوانی کند (برای مثال، داخل شنونده رویداد DOMContentLoaded، مقداردهی اولیه ماژول، یا قلاب نصب مؤلفه چارچوب مانند useEffect).
  • همگام‌سازی به‌روزرسانی‌های مدیریت:
    • برنامه باید بلافاصله پس‌از واکشی، signalAllAcceptedCredentials() را در کنترل‌کننده کلیک حذف اعتبارنامه فراخوانی کند.
    • برنامه باید بلافاصله پس‌از واکشی، signalCurrentUserDetails() را در کنترل‌کننده کلیک تغییر نام حساب کاربری (name یا displayName) فراخوانی کند.
// Client-side management synchronization ES module
import { listFetch, renameCredentialFetch, updateUserFetch, deleteFetch } from './api.js';

// Base64URL-encoded User ID string (illustration only)
const base64UrlUserId = "M2YPl-KGnA8";

async function syncAcceptedCredentials(currentCredentialsList) {
  if (!window.PublicKeyCredential || !PublicKeyCredential.signalAllAcceptedCredentials) return;
  try {
    const credentialIds = currentCredentialsList.map(c => c.id); // Array of Base64URL credential ID strings
    
    await PublicKeyCredential.signalAllAcceptedCredentials({
      rpId, // RP ID must match the one defined on the server
      userId: base64UrlUserId, // User ID Base64URL-encoded string
      allAcceptedCredentialIds: credentialIds
    });
  } catch (e) {
    console.error('SignalAllAcceptedCredentials sync failure:', e);
  }
}

async function loadManagementPanel() {
  const response = await listFetch();
  const list = await response.json();
  
  renderUI(list);
  // Sync accepted credentials on initial load
  await syncAcceptedCredentials(list);
}

async function performDelete(credentialId) {
  const response = await deleteFetch(credentialId);
  if (response.ok) {
    const updatedResponse = await listFetch();
    const updatedList = await updatedResponse.json();
    
    renderUI(updatedList);
    // Sync remaining accepted credentials after deletion
    await syncAcceptedCredentials(updatedList);
  }
}

// Renaming a passkey's nickname updates the server record only
async function performCredentialRename(credentialId, newCredentialName) {
  const response = await renameCredentialFetch(credentialId, { name: newCredentialName });
  if (response.ok) {
    const updatedResponse = await listFetch();
    renderUI(await updatedResponse.json());
  }
}

// Renaming the user's account username or displayName triggers signalCurrentUserDetails
async function performUserRename(rpId, userId, updatedName, updatedDisplayName) {
  const response = await updateUserFetch({ name: updatedName, displayName: updatedDisplayName });
  if (response.ok && window.PublicKeyCredential && PublicKeyCredential.signalCurrentUserDetails) {
    try {
      await PublicKeyCredential.signalCurrentUserDetails({
        rpId, // RP ID must match the one defined on the server
        userId, // Base64URL-encoded user ID
        name: updatedName, // Updated username
        displayName: updatedDisplayName // Updated display name
      });
    } catch (e) {
      console.error('SignalCurrentUserDetails sync failure:', e);
    }
  }
}

window.addEventListener('DOMContentLoaded', loadManagementPanel);

تعیین ارائه‌دهنده گذرکلید از AAGUID

‫AAGUID (شناسه منحصربه‌فرد جهانی گواهی اصالت‌سنج) شناسه‌ای ۱۲۸ بیتی است که مدل اصالت‌سنج را نشان می‌دهد، نه یک نمونه خاص را. این شناسه درطول ثبت گذرکلید در داده‌های اصالت‌سنج گنجانده می‌شود و می‌توان از آن برای تعیین اینکه کدام ارائه‌دهنده گذرکلید (برای مثال، «مدیر گذرواژه Google»،‏ iCloud Keychain،‏ 1Password) اعتبارنامه را ایجاد کرده است استفاده کرد.

از AAGUID فقط باید برای کمک به کاربران در مدیریت گذرکلید استفاده شود. می‌توان آن را تغییر داد، مگراینکه ازنظر رمزنگاری تأیید شده باشد، که درحال‌حاضر گذرکلیدهای پلاتفرم از آن پشتیبانی نمی‌کنند.

۱. AAGUID Registry

نگاشت JSON نگهداری‌شده توسط انجمن از AAGUID به نام‌ها و نمادهای ارائه‌دهنده در اینجا دردسترس است:

https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/refs/heads/main/combined_aaguid.json

هر ورودی طرح‌واره زیر را دارد:

{
  "<aaguid-uuid>": {
    "name": "Provider Name",
    "icon_light": "data:image/png;base64,...",
    "icon_dark": "data:image/png;base64,..."
  }
}

۲. استفاده از AAGUID پس‌از ثبت‌نام

پس‌از درستی‌سنجی پاسخ ثبت‌نام، aaguid را از نتیجه ثبت‌نام بخوانید و آن را با دفتر ثبت مقایسه کنید تا name و providerIcon اطلاعات کاربری را پر کنید:

قبل‌از جستجوی AAGUID در دفتر ثبت، بررسی کنید که آیا با '00000000-0000-0000-0000-000000000000' برابر است یا نه. If so, skip the registry lookup and set name to a fallback (e.g. device name from user-agent, or "Unknown passkey provider") and providerIcon to undefined. فقط برای AAGUIDهای غیرصفر، رجیستری را جستجو کنید.

import aaguids from './aaguids.json' with { type: 'json' };

const { aaguid } = registrationInfo;
if (aaguid === '00000000-0000-0000-0000-000000000000') {
  // use the device name as the passkey provider based on
  // the information derived from the user agent string,
  // or just say "Unknown passkey provider"
} else {
  const provider = aaguids[aaguid];
  const credential = {
    // ...other fields
    aaguid,
    name: provider?.name || 'Unknown passkey provider',
    providerIcon: provider?.icon_light,
  };
}

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

جایگزین تشخیص ویژگی گذرکلید

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

import 'webauthn-polyfills';

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

اگر مرورگر از PublicKeyCredential.parseRequestOptionsFromJSON پشتیبانی نمی‌کند، از «webauthn-polyfills» استفاده کنید:

<script type="module"> 
  if (!PublicKeyCredential.parseRequestOptionsFromJSON) { 
     await import('https://unpkg.com/webauthn-polyfills'); 
   } 
 </script> 

این کار همچنین پشتیبانی از PublicKeyCredential.prototype.toJSON را اضافه می‌کند.