يوضّح هذا الدليل بالتفصيل كيفية السماح للمستخدمين بتسجيل مفتاح مرور لحساباتهم، ما يوفّر بديلاً آمنًا للغاية لتسجيل الدخول بدون كلمة مرور ومقاومًا للتصيّد الاحتيالي.
متطلبات قاعدة البيانات
لإتاحة عمليات تسجيل مفاتيح المرور، يجب أن يخزّن جدول بيانات الاعتماد في قاعدة البيانات الحقول التالية:
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. الاعتماد على مكتبة تم فحصها وفقًا لمعايير الفئة بدلاً من استخدام التشفير اليدوي
- استخدام معرّف الجهة المعتمدة المحدّد مسبقًا: استخدِم معرّف الجهة المعتمدة المناسب المحدّد مسبقًا كسلسلة ثابتة.
- إنشاء تحدٍّ آمن: أنشِئ مخزنًا مؤقتًا عشوائيًا عالي الإنتروبيا وآمنًا من الناحية التشفيرية على الخادم، وخزِّنه بأمان في جلسة المستخدم، وشفِّره باستخدام Base64URL لتسليم الخيارات.
- تجنُّب تكرار مفاتيح المرور: يمكنك ربط معرّفات بيانات الاعتماد الحالية المسجّلة مسبقًا للمستخدم بمصفوفة خيارات
excludeCredentials. يمنع ذلك أداة المصادقة من تسجيل بيانات اعتماد مكرّرة على حساب مقدّم مفتاح المرور نفسه. - فرض بيانات الاعتماد القابلة للاكتشاف: اضبط
requireResidentKey: trueوresidentKey: "required"في خياراتauthenticatorSelectionلطلب بيانات اعتماد قابلة للاكتشاف، وهو أمر ضروري لعمليات تسجيل الدخول القابلة للاكتشاف. - ضبط إعدادات "التحقّق من المستخدم": حدِّد
userVerification: "preferred"أوuserVerification: "required". تتطلب العديد من حالات استخدام الامتثال (مثل الشؤون المالية والرعاية الصحية)'required'لفرض التحقق من المستخدم عند الإنشاء. - تحديد نطاق المرفقات:
- مسار الترويج: عند اقتراح إنشاء مفتاح مرور بعد تسجيل الدخول باستخدام كلمة مرور عادية أو بعد الاشتراك، اضبط
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" }),
},
};
التحقق
- التحقّق من التحدّي: يمكنك التحقّق من التحدّي بأمان مقارنةً بالتحدّي المتوقّع المرتبط بالجلسة.
- التحقّق من حضور المستخدم:
- يُرجى التأكّد من أنّ علامة "المستخدم حاضر" (UP) التي تم عرضها في بيانات أداة المصادقة التي تم تحليلها هي
trueلتأكيد حضور المستخدم فعليًا في وقت الإنشاء.
- يُرجى التأكّد من أنّ علامة "المستخدم حاضر" (UP) التي تم عرضها في بيانات أداة المصادقة التي تم تحليلها هي
- تخفيف متطلبات التحقّق من الهوية في حالة "مفضّل":
- عندما تحدّد خيارات الإنشاء
userVerification: "preferred"، يجب ضبط طلب التحقّق من جهة الخادم باستخدامrequireUserVerification: false. وإلا، فإنّ أدوات المصادقة التي تسجّل بدون التحقّق من المستخدم (على سبيل المثال، أقفال الشاشة غير مفعّلة) ستؤدي إلى حدوث أخطاء زائفة في التحقّق من الخادم.
- عندما تحدّد خيارات الإنشاء
المنطق من جهة العميل
- حظر واجهة المستخدم عند تحميل الصفحة:
- عند تحميل الصفحة، يمكنك استدعاء
PublicKeyCredential.getClientCapabilities()وإيقاف الزر "إنشاء مفتاح مرور" في حال عدم توفّرconditionalGetأوpasskeyPlatformAuthenticator.
- عند تحميل الصفحة، يمكنك استدعاء
- استدعاء الإنشاء والتسلسل: يمكنك فك ترميز خيارات الخادم باستخدام
PublicKeyCredential.parseCreationOptionsFromJSON()وتمرير الإعدادات الناتجة إلىnavigator.credentials.create().- يجب استدعاء
credential.toJSON()لترميزAuthenticatorAttestationResponseإلى عنصر صالح يمكن تسلسله بتنسيق JSON قبل جلب نقطة نهاية التحقّق.
- يجب استدعاء
- التعامل مع استثناءات WebAuthn:
-
InvalidStateError: سبق أن تم إعداد مفتاح مرور مطابق (تمت المطابقة حسبexcludeCredentials). -
NotAllowedError: ألغى المستخدم مربّع حوار مفتاح مرور المصادقة أو انتهت مهلته. -
AbortError: تم إلغاء العملية. -
SecurityError: أخطاء عدم تطابق المصادر الآمنة (HTTPS) أو معرّف الجهة المعتمدة (مشاكل في الإعدادات)
-
- فصل Try/Catch في Signal API:
- يجب تضمين طلب التحقّق من جهة الخادم
fetch()في كتلة try/catch. يتم استدعاءsignalUnknownCredential()عندما يتعذّر جلب عملية التحقّق من الخادم (أي حالةresponse.ok === falseأو أخطاء في الشبكة).
- يجب تضمين طلب التحقّق من جهة الخادم
// 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';