يوضّح هذا الدليل بالتفصيل كيفية تنفيذ مصادقة المستخدمين المكرّري الزيارة باستخدام بيانات الاعتماد القابلة للاكتشاف، من خلال كلّ من عوامل تشغيل الأزرار الصريحة واقتراحات الملء التلقائي السلسة في المتصفّح (واجهة المستخدم الشَرطية).
من جهة الخادم
إنشاء خيارات
أنشِئ نقطة نهاية تنشئ مَعلمات طلب WebAuthn باستخدام مكتبة تم فحصها وفقًا للمعايير.
- استخدام معرّف الجهة المعتمدة المحدّد مسبقًا: استخدِم معرّف الجهة المعتمدة المناسب المحدّد مسبقًا كسلسلة ثابتة.
- إنشاء اختبار: أنشِئ مخزنًا مؤقتًا عشوائيًا آمنًا من الناحية التشفيرية وذا إنتروبيا عالية، وخزِّنه بأمان في جلسة المستخدم، ورمِّزه بتنسيق Base64URL.
- ربط بيانات الاعتماد القابلة للاكتشاف: حدِّد مصفوفة فارغة
[]لـallowCredentials. يطلب هذا الإجراء بيانات اعتماد قابلة للاكتشاف، ما يعني أنّ المستخدم لا يحتاج إلى إدخال اسم المستخدم أولاً، بل سيعرض مقدّم مفتاح المرور الحسابات المتاحة. - مستوى التحقّق من المستخدم: اضبطه على
userVerification: "preferred"(أو"required"إذا كان ذلك مطلوبًا صراحةً بموجب سياسات الامتثال في الشركة).- يجب الاحتفاظ بمستوى قيد
userVerificationالمطلوب داخل سجلّ جلسة الخادم في نقطة نهاية الخيارات، بدلاً من تمريره من العميل عبر سلاسل طلبات البحث. يتيح ذلك لنقطة نهاية التحقّق فرض قيود مطابقة صارمة بأمان بدون خطر تلاعب العميل.
- يجب الاحتفاظ بمستوى قيد
// Options generation example (discoverable flow)
const options = {
challenge: serverGeneratedBase64UrlChallenge, // High-entropy random challenge stored in session
rpId: "example.com",
allowCredentials: [], // Request discoverable passkeys
userVerification: "preferred",
};
// Persist expected UV level to user session
req.session.expectedUserVerification = "preferred";
نقطة نهاية التحقّق
تحقَّق بأمان من التأكيد الذي يعرضه العميل لمصادقة المستخدم:
- التحقّق من صحة اختبار الجلسة: يمكنك فرض مطابقة صارمة للاختبار بين استجابة العميل والاختبار المتوقّع المخزَّن في الجلسة.
- فرض الإعدادات المفضّلة للتحقّق من المستخدم:
- السماح بأدوات المصادقة التي لا تتطلّب التحقّق من المستخدم (مثل أقفال شاشة أداة المصادقة غير المفعَّلة) إذا طلبت الجلسة
expectedUserVerification"preferred"، من خلال تمريرrequireUserVerification: falseإلى مكتبة التحقّق من جهة الخادم إذا طُلب منك ذلك من قِبل"required"، عليك فرض إدخال المقاييس الحيوية أو رقم التعريف الشخصي بشكل صارم.
- السماح بأدوات المصادقة التي لا تتطلّب التحقّق من المستخدم (مثل أقفال شاشة أداة المصادقة غير المفعَّلة) إذا طلبت الجلسة
- خطأ في الخادم 404: إذا لم يتم العثور على معرّف بيانات الاعتماد الذي يعرضه العميل في قاعدة البيانات، يجب عرض خطأ في بروتوكول HTTP
404صريح ليتمكّن العميل من تشغيل Signal API.
المنطق من جهة العميل
تعليق توضيحي لنموذج HTML
أضِف تعليقات توضيحية إلى حقول إدخال اسم المستخدم وكلمة المرور للاستفادة من واجهة المستخدم المشروطة بشكلٍ أصلي. تجمع رموز الإكمال التلقائي بين مَعلمات مواصفات WebAuthn، ويؤدي التركيز التلقائي إلى تشغيل النافذة المنبثقة للملء التلقائي في المتصفّح فورًا عند التركيز على حقل الإدخال.
<!-- Autocomplete tokens must contain webauthn space-separated -->
<form id="signin-form">
<input
type="text"
name="username"
autocomplete="username webauthn"
autofocus
data-testid="username-field"
/>
<input type="password" name="password" autocomplete="current-password" />
<button type="submit">Sign in</button>
</form>
مسار الزر الصريح
يمكنك تفعيل المصادقة باستخدام مفتاح المرور عندما ينقر المستخدم على زر "تسجيل الدخول باستخدام مفتاح المرور". إلغاء أي طلبات جارية للملء التلقائي للنماذج (Conditional Get) قبل استدعاء طلب مفتاح المرور
مسار التوسّط الشرطي (الملء التلقائي للنموذج)
فعِّل اقتراحات الملء التلقائي للنماذج عند تحميل الصفحة لتقديم مصادقة مفتاح المرور بشكلٍ أصلي عندما يركّز المستخدمون على حقول تسجيل الدخول:
- رصد الميزة: يمكنك استدعاء
PublicKeyCredential.getClientCapabilities()عند تحميل الصفحة وتخطّي تسجيل الدخول باستخدام مفتاح المرور إذا لم يكنconditionalGetمتاحًا. - خيارات فك الترميز: فك ترميز كائن JSON لبيانات الاعتماد التي تم جلبها باستخدام
PublicKeyCredential.parseRequestOptionsFromJSON(). - استدعاء Get الشرطي: يمكنك استدعاء
navigator.credentials.get()باستخدامmediation: "conditional"وتمرير إشارةAbortController. يؤدي ذلك إلى تسجيل ميزة "الملء التلقائي" بدون عرض مربّع حوار مفتاح المرور. - فصل استثناءات Try/Catch: يمكنك تضمين طلب
navigator.credentials.getفي مجموعة try/catch:-
NotAllowedError: ألغى المستخدم طلب تسجيل الدخول باستخدام مفتاح المرور أو انتهت مهلة الطلب. -
AbortError: تم إلغاء طلب المصادقة آليًا.
-
- Call Signal API: يمكنك تضمين طلب التحقّق من الخادم
fetch()في كتلة try/catch:- عرض رسالة خطأ للمستخدم ليفهم المشكلة
- يجب الاتصال بـ
signalUnknownCredential()فقط عندما يستجيب الخادم صراحةً بحالة HTTP404(لم يتم العثور على بيانات الاعتماد) ويكون المستخدم غير مصادَق عليه. - يجب أن تكون المَعلمة
credentialIdالتي تم تمريرها إلىsignalUnknownCredential()هي سلسلة معرّف بيانات الاعتماد المرمَّزة بنظام Base64URL (مثلencoded.id)، وليس عنصر ArrayBuffer الأوليcredential.rawId.
- ترميز الردّ: يمكنك ترميز بيانات الاعتماد
AuthenticatorAssertionResponseباستخدام.toJSON()قبل إرسالها إلى الخادم لإثبات الهوية.
// optionsFetch and loginVerifyFetch are app-defined HTTP methods
import { optionsFetch, loginVerifyFetch } from "./api.js";
let autofillAbortController = new AbortController();
async function initializeConditionalAutofill() {
// Feature detect Conditional Get autofill support
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet === true) {
const loginOptionsJSON = await optionsFetch();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);
try {
// Initiate Conditional UI form autofill suggestions
const credential = await navigator.credentials.get({
publicKey,
signal: autofillAbortController.signal,
mediation: "conditional",
});
// Segregated verification fetch
const encoded = credential.toJSON();
const response = await loginVerifyFetch(encoded);
if (!response.ok && response.status === 404) {
// Note: this code path runs pre-authentication, satisfying the unauth precondition
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encoded.id,
});
}
}
} catch (err) {
// Silently swallow expected client WebAuthn exceptions
if (["NotAllowedError", "AbortError"].includes(err.name)) {
return;
}
console.error("Unexpected conditional get error:", err);
}
}
}
async function triggerButtonAuthentication() {
// Abort any pending Conditional Get call to prevent passkey prompt collisions
autofillAbortController.abort();
autofillAbortController = new AbortController(); // Reset controller for next triggers
const loginOptionsJSON = await optionsFetch();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);
let credential;
try {
// Passkey explicit prompt trigger
credential = await navigator.credentials.get({
publicKey,
signal: autofillAbortController.signal,
});
} catch (err) {
if (err.name === "NotAllowedError") {
console.log("User cancelled passkey login.");
} else if (err.name === "AbortError") {
console.log("The authentication operation was aborted.");
}
// Re-arm Conditional autofill Suggestions after cancelled explicit button prompts
initializeConditionalAutofill();
return; // Safe exit
}
// Segregated verification try/catch (HTTP 404 trigger)
const encoded = credential.toJSON();
try {
const response = await loginVerifyFetch(encoded);
if (!response.ok && response.status === 404) {
// Note: this code path runs pre-authentication, satisfying the unauth precondition
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encoded.id, // Base64URL-encoded credential ID
});
}
} catch (serverErr) {
console.error("Verification request error:", serverErr);
}
}
// Trigger Conditional Get on load
window.addEventListener("DOMContentLoaded", initializeConditionalAutofill);
الاستراتيجيات الاحتياطية
الاحتياطي لرصد ميزة مفتاح المرور
يجب دائمًا تثبيت "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.parseRequestOptionsFromJSON وPublicKeyCredential.prototype.toJSON متوافقَين أيضًا.
import 'webauthn-polyfills';