نظرة عامة
في ما يلي نظرة عامة على الخطوات الرئيسية المتّبعة في عملية تسجيل مفتاح المرور:
- حدِّد خيارات لإنشاء مفتاح مرور. أرسِلها إلى العميل حتى تتمكّن من تمريرها إلى طلب إنشاء مفتاح المرور: طلب WebAuthn API
navigator.credentials.create
على الويب، وcredentialManager.createCredential
على Android. بعد أن يؤكّد المستخدم إنشاء مفتاح المرور، يتم حلّ طلب إنشاء مفتاح المرور وعرض بيانات اعتمادPublicKeyCredential
. - تحقَّق من بيانات الاعتماد وخزِّنها على الخادم.
تتناول الأقسام التالية تفاصيل كل خطوة.
إنشاء خيارات إنشاء بيانات الاعتماد
أول خطوة عليك اتّخاذها على الخادم هي إنشاء عنصر PublicKeyCredentialCreationOptions
.
لإجراء ذلك، استخدِم مكتبة FIDO من جهة الخادم. وعادةً ما يوفّر دالة مساعدة يمكنها إنشاء هذه الخيارات لك. توفّر SimpleWebAuthn، على سبيل المثال، generateRegistrationOptions
.
يجب أن يتضمّن PublicKeyCredentialCreationOptions
كل ما يلزم لإنشاء مفتاح مرور: معلومات عن المستخدم وعن الجهة الاعتمادية، وإعدادًا لخصائص بيانات الاعتماد التي يتم إنشاؤها. بعد تحديد كل هذه العناصر، مرِّرها حسب الحاجة إلى الدالة في مكتبة FIDO من جهة الخادم المسؤولة عن إنشاء العنصر PublicKeyCredentialCreationOptions
.
يمكن أن تكون بعض حقول PublicKeyCredentialCreationOptions
ثوابت. يجب تحديد القيم الأخرى ديناميكيًا على الخادم:
rpId
: لملء حقل RP ID على الخادم، استخدِم الدوال أو المتغيرات من جهة الخادم التي تمنحك اسم مضيف تطبيق الويب، مثلexample.com
.user.name
وuser.displayName
: لملء هذه الحقول، استخدِم معلومات جلسة المستخدم الذي سجّل الدخول (أو معلومات حساب المستخدم الجديد، إذا كان المستخدم ينشئ مفتاح مرور عند الاشتراك).user.name
هو عادةً عنوان بريد إلكتروني، وهو فريد بالنسبة إلى الجهة الاعتمادية. user.displayName
هو اسم سهل الاستخدام. يُرجى العِلم أنّ بعض المنصات لن تستخدمdisplayName
.user.id
: سلسلة عشوائية وفريدة يتم إنشاؤها عند إنشاء الحساب. يجب أن يكون دائمًا، على عكس اسم المستخدم الذي يمكن تعديله. يحدّد معرّف المستخدم حسابًا، ولكن يجب ألا يحتوي على أي معلومات تحديد الهوية الشخصية. من المحتمل أنّ لديك معرّف مستخدم في نظامك، ولكن إذا لزم الأمر، يمكنك إنشاء معرّف خاص بمفاتيح المرور لإبقائه خاليًا من أي معلومات تعريف شخصية.excludeCredentials
: قائمة بأرقام تعريف بيانات الاعتماد الحالية لمنع تكرار مفتاح مرور من موفّر مفاتيح المرور. لملء هذا الحقل، ابحث في قاعدة البيانات عن بيانات الاعتماد الحالية لهذا المستخدم. راجِع التفاصيل في مقالة منع إنشاء مفتاح مرور جديد إذا كان هناك مفتاح حالي.challenge
: بالنسبة إلى تسجيل بيانات الاعتماد، لا يكون اختبار التحقّق ذا صلة إلا إذا كنت تستخدم التصديق، وهو أسلوب أكثر تقدّمًا للتحقّق من هوية مقدّم مفتاح المرور والبيانات التي يصدرها. ومع ذلك، حتى إذا لم تستخدم شهادة التصديق، يظلّ الحقل "السؤال الأمني" مطلوبًا. تتوفّر تعليمات حول إنشاء تحدٍّ آمن للمصادقة في مقالة المصادقة باستخدام مفتاح مرور من جهة الخادم.
الترميز وفك الترميز

PublicKeyCredentialCreationOptions
التي أرسلها الخادم. يجب ترميز challenge
وuser.id
وexcludeCredentials.credentials
من جهة الخادم إلى base64URL
، لكي يمكن عرض PublicKeyCredentialCreationOptions
عبر HTTPS.تتضمّن PublicKeyCredentialCreationOptions
حقولاً هي ArrayBuffer
، لذا لا تتوافق مع JSON.stringify()
. هذا يعني أنّه في الوقت الحالي، من أجل عرض PublicKeyCredentialCreationOptions
عبر HTTPS، يجب ترميز بعض الحقول يدويًا على الخادم باستخدام base64URL
ثم فك ترميزها على العميل.
- على الخادم، تتولّى عادةً مكتبة FIDO من جهة الخادم عملية الترميز وفك الترميز.
- على الجهاز العميل، يجب إجراء الترميز وفك الترميز يدويًا في الوقت الحالي. سيكون ذلك أسهل في المستقبل، إذ ستتوفّر طريقة لتحويل الخيارات بتنسيق JSON إلى
PublicKeyCredentialCreationOptions
. اطّلِع على حالة التنفيذ في Chrome.
نموذج الرمز البرمجي: إنشاء خيارات إنشاء بيانات الاعتماد
نستخدم مكتبة SimpleWebAuthn في أمثلتنا. في هذه الخطوة، نسلم عملية إنشاء خيارات بيانات اعتماد المفتاح العام إلى الدالة generateRegistrationOptions
.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
generateAuthenticationOptions,
verifyAuthenticationResponse
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/registerRequest', csrfCheck, sessionCheck, async (req, res) => {
const { user } = res.locals;
// Ensure you nest verification function calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// `excludeCredentials` prevents users from re-registering existing
// credentials for a given passkey provider
const excludeCredentials = [];
const credentials = Credentials.findByUserId(user.id);
if (credentials.length > 0) {
for (const cred of credentials) {
excludeCredentials.push({
id: isoBase64URL.toBuffer(cred.id),
type: 'public-key',
transports: cred.transports,
});
}
}
// Generate registration options for WebAuthn create
const options = await generateRegistrationOptions({
rpName: process.env.RP_NAME,
rpID: process.env.HOSTNAME,
userID: user.id,
userName: user.username,
userDisplayName: user.displayName || '',
attestationType: 'none',
excludeCredentials,
authenticatorSelection: {
authenticatorAttachment: 'platform',
requireResidentKey: true
},
});
// Keep the challenge in the session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).send({ error: e.message });
}
});
تخزين المفتاح العام

navigator.credentials.create
الكائن PublicKeyCredential
.عندما يتم حلّ navigator.credentials.create
بنجاح على الجهاز، يعني ذلك أنّه تم إنشاء مفتاح مرور بنجاح. يتم عرض عنصر PublicKeyCredential
.
يحتوي الكائن PublicKeyCredential
على الكائن AuthenticatorAttestationResponse
الذي يمثّل استجابة مقدّم مفتاح المرور لتعليمات العميل بإنشاء مفتاح مرور. تحتوي على معلومات حول بيانات الاعتماد الجديدة التي تحتاج إليها بصفتك جهة موفِّرة للخدمة من أجل مصادقة المستخدم لاحقًا. يمكنك الاطّلاع على مزيد من المعلومات عن AuthenticatorAttestationResponse
في الملحق: AuthenticatorAttestationResponse
.
أرسِل العنصر PublicKeyCredential
إلى الخادم. بعد تلقّيه، عليك تأكيده.
سلِّم خطوة التحقّق هذه إلى مكتبة FIDO من جهة الخادم. وعادةً ما يوفّر وظيفة مساعدة لهذا الغرض. توفّر SimpleWebAuthn، على سبيل المثال، verifyRegistrationResponse
. يمكنك الاطّلاع على تفاصيل حول ما يحدث في الخلفية في الملحق: التحقّق من صحة رد التسجيل.
بعد إكمال عملية إثبات الهوية بنجاح، خزِّن معلومات بيانات الاعتماد في قاعدة البيانات حتى يتمكّن المستخدم لاحقًا من إثبات هويته باستخدام مفتاح المرور المرتبط ببيانات الاعتماد هذه.
استخدِم جدولاً مخصّصًا لبيانات اعتماد المفتاح العام المرتبطة بمفاتيح المرور. يمكن للمستخدم الحصول على كلمة مرور واحدة فقط، ولكن يمكنه الحصول على مفاتيح مرور متعددة، مثل مفتاح مرور تتم مزامنته من خلال "سلسلة مفاتيح iCloud" من Apple ومفتاح آخر من خلال "مدير كلمات المرور في Google".
في ما يلي مثال على مخطط يمكنك استخدامه لتخزين معلومات بيانات الاعتماد:
- جدول المستخدمين:
- استبدِل
user_id
برقم تعريف المستخدِم الأساسي. معرّف عشوائي وفريد ودائم للمستخدم. استخدِم هذا العمود كمفتاح أساسي لجدول المستخدمين. username
: اسم مستخدم يحدّده المستخدم، ويمكن تعديله.passkey_user_id
: معرّف المستخدم الخاص بمفتاح المرور والذي لا يتضمّن معلومات تحديد الهوية الشخصية، ويتم تمثيله بـuser.id
في خيارات التسجيل. عندما يحاول المستخدم المصادقة لاحقًا، ستوفّر أداة المصادقة هذاpasskey_user_id
في ردّ المصادقة فيuserHandle
. ننصحك بعدم ضبطpasskey_user_id
كمفتاح أساسي. تميل المفاتيح الأساسية إلى أن تصبح معلومات تعريف شخصية بحكم الواقع في الأنظمة، لأنّها تُستخدَم على نطاق واسع.
- استبدِل
- جدول بيانات الاعتماد باستخدام المفتاح العام:
id
: معرّف الاعتماد استخدِم هذا العمود كمفتاح أساسي لجدول بيانات اعتماد المفتاح العام.-
public_key
: المفتاح العام لبيانات الاعتماد. passkey_user_id
: استخدِم هذا الحقل كمفتاح خارجي لإنشاء رابط مع جدول المستخدمين.backed_up
: يتم الاحتفاظ بنسخة احتياطية من مفتاح المرور إذا تمت مزامنته من خلال مقدّم خدمة مفاتيح المرور. يكون تخزين حالة النسخ الاحتياطي مفيدًا إذا كنت تريد التفكير في إيقاف كلمات المرور في المستقبل للمستخدمين الذين يملكون مفاتيح مرورbacked_up
. يمكنك التحقّق مما إذا تم الاحتفاظ بنسخة احتياطية من مفتاح المرور من خلال فحص العلامة BE فيauthenticatorData
، أو باستخدام ميزة مكتبة من جهة الخادم FIDO التي تتوفّر عادةً لتسهيل الوصول إلى هذه المعلومات. قد يكون تخزين أهلية النسخ الاحتياطي مفيدًا للردّ على استفسارات المستخدمين المحتملة.-
name
: اسم العرض الخاص ببيانات الاعتماد، وهو اختياري ويسمح للمستخدمين بتسمية بيانات الاعتماد بأسماء مخصّصة. - استبدِل
transports
بصفيف من وسائل النقل. يُعدّ تخزين وسائل النقل مفيدًا لتحسين تجربة المستخدم في المصادقة. عندما تكون وسائل النقل متاحة، يمكن للمتصفّح أن يتصرّف وفقًا لذلك ويعرض واجهة مستخدم تتطابق مع وسيلة النقل التي يستخدمها مقدّم مفتاح المرور للتواصل مع العملاء، لا سيما في حالات إعادة المصادقة التي لا تكون فيهاallowCredentials
فارغة.
يمكن أن تكون المعلومات الأخرى مفيدة للتخزين لأغراض تجربة المستخدم، بما في ذلك عناصر مثل مقدّم مفتاح المرور ووقت إنشاء بيانات الاعتماد ووقت آخر استخدام. يمكنك الاطّلاع على مزيد من المعلومات في تصميم واجهة مستخدم مفاتيح المرور.
مثال على الرمز البرمجي: تخزين بيانات الاعتماد
نستخدم مكتبة SimpleWebAuthn في أمثلتنا.
هنا، نسلم مهمة التحقّق من صحة رد التسجيل إلى الدالة verifyRegistrationResponse
.
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/registerResponse', csrfCheck, sessionCheck, async (req, res) => {
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
const response = req.body;
// This sample code is for registering a passkey for an existing,
// signed-in user
// Ensure you nest verification function calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Verify the credential
const { verified, registrationInfo } = await verifyRegistrationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
requireUserVerification: false,
});
if (!verified) {
throw new Error('Verification failed.');
}
const {
aaguid,
credentialPublicKey,
credentialID,
credentialBackedUp
} = registrationInfo;
// Name the credential based on AAGUID
const name =
aaguid === undefined ||
aaguid === '000000-0000-0000-0000-00000000' ?
req.useragent?.platform : aaguids[aaguid].name;
const base64CredentialID = isoBase64URL.fromBuffer(credentialID);
const base64PublicKey = isoBase64URL.fromBuffer(credentialPublicKey);
// Existing, signed-in user
const { user } = res.locals;
// Save the credential
await Credentials.update({
id: base64CredentialID,
passkey_user_id: user.passkey_user_id,
publicKey: base64PublicKey,
name,
aaguid,
transports: response.response.transports,
backed_up: credentialBackedUp,
registered_at: new Date().getTime()
});
// Kill the challenge for this session
delete req.session.challenge;
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).send({ error: e.message });
}
});
الملحق: AuthenticatorAttestationResponse
يحتوي AuthenticatorAttestationResponse
على عنصرَين مهمَّين:
-
response.clientDataJSON
هو إصدار JSON من بيانات العميل، وهي على الويب البيانات التي يراها المتصفّح. يحتوي على مصدر RP والتحدي وandroidPackageName
إذا كان العميل تطبيق Android. بصفتك RP، يتيح لك قراءةclientDataJSON
الوصول إلى المعلومات التي رآها المتصفح في وقت طلبcreate
. response.attestationObject
يتضمّن معلوماتَين:attestationStatement
التي لا تكون ذات صلة إلا إذا كنت تستخدم خدمة إثبات صحة الجهاز- تمثّل
authenticatorData
البيانات كما يراها مزوّد مفتاح المرور. بصفتك "جهة معتمدة"، يمنحك إذنauthenticatorData
إمكانية الوصول إلى البيانات التي يراها مقدّم مفتاح المرور والتي يتم عرضها عند تقديم طلبcreate
.
authenticatorData
تحتوي على معلومات أساسية حول بيانات المفتاح العام المرتبطة بمفتاح المرور الذي تم إنشاؤه حديثًا:
- بيانات اعتماد المفتاح العام نفسها، ومعرّف فريد لبيانات الاعتماد
- معرّف الجهة المحظورة المرتبط ببيانات الاعتماد
- علامات تصف حالة المستخدم عند إنشاء مفتاح المرور: ما إذا كان المستخدم متواجدًا فعليًا، وما إذا تم إثبات هويته بنجاح (راجِع نظرة تفصيلية على userVerification).
- معرّف AAGUID هو معرّف لموفّر مفتاح المرور، مثل "مدير كلمات المرور في Google". استنادًا إلى AAGUID، يمكنك تحديد موفّر مفتاح المرور وعرض الاسم في صفحة إدارة مفاتيح المرور. (راجِع تحديد مقدّم مفتاح المرور باستخدام AAGUID)
على الرغم من أنّ authenticatorData
مضمّنة في attestationObject
، إلا أنّ المعلومات التي تحتوي عليها مطلوبة لتنفيذ مفتاح المرور سواء كنت تستخدم شهادة المصادقة أم لا. يتم ترميز authenticatorData
، وهي تحتوي على حقول يتم ترميزها بتنسيق ثنائي. ستتولّى مكتبة من جهة الخادم عادةً عملية التحليل والتشفير. إذا كنت لا تستخدم مكتبة من جهة الخادم، ننصحك بالاستفادة من getAuthenticatorData()
من جهة العميل لتوفير بعض الجهد في تحليل البيانات وفك تشفيرها من جهة الخادم.
الملحق: التحقّق من صحة رد التسجيل
تتضمّن عملية التحقّق من رد التسجيل في الخلفية عمليات التحقّق التالية:
- تأكَّد من أنّ رقم تعريف الشريك الإعلاني يتطابق مع موقعك الإلكتروني.
- تأكَّد من أنّ مصدر الطلب هو مصدر متوقّع لموقعك الإلكتروني (عنوان URL الرئيسي للموقع الإلكتروني، تطبيق Android).
- إذا كنت تشترط إثبات هوية المستخدم، تأكَّد من أنّ علامة إثبات هوية المستخدم
authenticatorData.uv
هيtrue
. - من المتوقّع عادةً أن تكون قيمة علامة حضور المستخدم
authenticatorData.up
هيtrue
، ولكن إذا تم إنشاء بيانات الاعتماد بشكل مشروط، من المتوقّع أن تكون القيمةfalse
. - تأكَّد من أنّ العميل تمكّن من تقديم الردّ على التحدّي الذي طرحته عليه. إذا كنت لا تستخدم خدمة "التصديق"، لن يكون هذا التحقّق مهمًا. ومع ذلك، يُعدّ تنفيذ عملية التحقّق هذه من أفضل الممارسات، لأنّها تضمن أنّ التعليمات البرمجية جاهزة إذا قرّرت استخدام خدمة "إثبات صحة الجهاز" في المستقبل.
- تأكَّد من عدم تسجيل معرّف بيانات الاعتماد لأي مستخدم حتى الآن.
- تأكَّد من أنّ الخوارزمية التي يستخدمها مقدّم مفتاح المرور لإنشاء بيانات الاعتماد هي خوارزمية أدرجتها (في كل حقل
alg
منpublicKeyCredentialCreationOptions.pubKeyCredParams
، والذي يتم تحديده عادةً ضمن مكتبة من جهة الخادم ولا يظهر لك). يضمن ذلك ألا يتمكّن المستخدمون من التسجيل إلا باستخدام الخوارزميات التي اخترت السماح بها.
لمزيد من المعلومات، يمكنك الاطّلاع على الرمز المصدري verifyRegistrationResponse
الخاص بمكتبة SimpleWebAuthn أو التعرّف على القائمة الكاملة لعمليات التحقّق في المواصفات.