نظرة عامة
في ما يلي نظرة عامة عالية المستوى على الخطوات الأساسية لتسجيل مفتاح المرور:
- حدِّد خيارات إنشاء مفتاح مرور. عليك إرسالها إلى العميل، لتتمكّن من تمريرها إلى طلب إنشاء مفتاح المرور: اتصال WebAuthn API على الويب
navigator.credentials.create
على الويب وcredentialManager.createCredential
على Android. بعد تأكيد المستخدم على إنشاء مفتاح المرور، يتم حلّ طلب إنشاء مفتاح المرور ويتم عرض بيانات اعتمادPublicKeyCredential
. - تحقَّق من بيانات الاعتماد وخزِّنها على الخادم.
تتناول الأقسام التالية تفاصيل كل خطوة.
إنشاء خيارات إنشاء بيانات الاعتماد
الخطوة الأولى التي يجب اتّخاذها على الخادم هي إنشاء عنصر PublicKeyCredentialCreationOptions
.
ولإجراء ذلك، يمكنك الاعتماد على مكتبة FIDO في الخادم. وستقدم عادةً وظيفة مساعدة يمكنها إنشاء هذه الخيارات لك. عروض SimpleWebAuthn، على سبيل المثال، generateRegistrationOptions
.
يجب أن يتضمّن PublicKeyCredentialCreationOptions
كل ما هو مطلوب لإنشاء مفتاح المرور: معلومات عن المستخدم وحول الجهة المحظورة وإعداد خصائص بيانات الاعتماد التي تنشئها. بعد تحديد كل هذه العناصر، مرِّرها حسب الحاجة إلى الدالة في مكتبة FIDO لجهة الخادم المسؤولة عن إنشاء العنصر PublicKeyCredentialCreationOptions
.
يمكن أن تكون بعض حقول PublicKeyCredentialCreationOptions
ثوابتًا. ويجب تحديد السمات الأخرى ديناميكيًا على الخادم:
rpId
: لتعبئة رقم تعريف الجهة المحظورة على الخادم، استخدِم الدوال أو المتغيّرات من جهة الخادم التي تمنحك اسم المضيف لتطبيق الويب، مثلexample.com
.user.name
وuser.displayName
: لملء هذه الحقول، استخدِم معلومات جلسة المستخدم الذي سجّل الدخول (أو معلومات حساب المستخدم الجديد، إذا كان المستخدم ينشئ مفتاح مرور عند الاشتراك). يكونuser.name
عادةً عنوان بريد إلكتروني، وهو فريد للجهة المحظورة. "user.displayName
" هو اسم سهل الاستخدام. يُرجى العلم أنّ بعض الأنظمة الأساسية لن تستخدمdisplayName
.user.id
: سلسلة عشوائية وفريدة يتم إنشاؤها عند إنشاء الحساب. ويجب أن يكون الاسم دائمًا، على عكس اسم المستخدم الذي قد يكون قابلاً للتعديل. يحدِّد رقم تعريف المستخدم حسابًا، ولكن يجب ألا يحتوي على أي معلومات لتحديد الهوية الشخصية (PII). من المحتمل أن يكون لديك رقم تعريف مستخدم في نظامك، ولكن إذا لزم الأمر، أنشئ واحدًا مخصّصًا لمفاتيح المرور للحفاظ على خلوه من أي معلومات تحديد الهوية الشخصية.excludeCredentials
: قائمة بأرقام تعريف بيانات الاعتماد الحالية لمنع تكرار مفتاح المرور من موفِّر مفتاح المرور. لتعبئة هذا الحقل، ابحث في بيانات الاعتماد الحالية لقاعدة البيانات الخاصة بك عن هذا المستخدم. يمكنك مراجعة التفاصيل في قسم منع إنشاء مفتاح مرور جديد في حال توفُّر مفتاح مرور.challenge
: بالنسبة إلى تسجيل بيانات الاعتماد، لا يُعدّ التحدي ملائمًا إلا إذا استخدمت المصادقة، وهي طريقة أكثر تقدّمًا لإثبات هوية موفِّر مفتاح المرور والبيانات التي يصدرها. ومع ذلك، حتى إذا لم تستخدم المصادقة، سيظل التحدي حقلاً مطلوبًا. في هذه الحالة، يمكنك ضبط هذا التحدي على0
واحد لتبسيط العملية. تتوفّر تعليمات إنشاء اختبار تحقق آمن للمصادقة في مصادقة مفتاح المرور من جهة الخادم.
الترميز وفك الترميز
![حقل PublicKeyCredentialCreationOptions الذي أرسله الخادم](https://developers-dot-devsite-v2-prod.appspot.com/identity/passkeys/developer-guides/server-registration/images/server-publicKeyCredentialCreationOptions.png?authuser=6&hl=ar)
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 = 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 });
}
});
تخزين المفتاح العام
![حقل PublicKeyCredentialCreationOptions الذي أرسله الخادم](https://developers-dot-devsite-v2-prod.appspot.com/identity/passkeys/developer-guides/server-registration/images/server-publicKeyCredentialCreationOptions.png?authuser=6&hl=ar)
navigator.credentials.create
كائن PublicKeyCredential
.عندما يتم حلّ مشكلة navigator.credentials.create
بنجاح في البرنامج، هذا يعني أنّه تم إنشاء مفتاح مرور بنجاح. يتم عرض عنصر PublicKeyCredential
.
يحتوي الكائن PublicKeyCredential
على عنصر AuthenticatorAttestationResponse
، وهو يمثّل ردّ موفِّر مفتاح المرور على تعليمات العميل لإنشاء مفتاح مرور. ويحتوي على معلومات حول بيانات الاعتماد الجديدة التي تحتاجها كجهة محظورة لمصادقة المستخدم لاحقًا. يمكنك الاطّلاع على مزيد من المعلومات عن "AuthenticatorAttestationResponse
" في الملحق: AuthenticatorAttestationResponse
.
إرسال عنصر "PublicKeyCredential
" إلى الخادم بعد استلام البطاقة، يُرجى إثبات ملكيتها.
سلِّم خطوة إثبات الملكية هذه إلى مكتبة FIDO على الخادم. وستقدم عادةً وظيفة فائدة لهذا الغرض. عروض SimpleWebAuthn، على سبيل المثال، verifyRegistrationResponse
. يمكنك التعرّف على مزيد من التفاصيل في الملحق: التحقّق من استجابة التسجيل.
بمجرد نجاح التحقق، خزّن معلومات الاعتماد في قاعدة البيانات الخاصة بك حتى يتمكن المستخدم من المصادقة لاحقًا باستخدام مفتاح المرور المرتبط ببيانات الاعتماد هذه.
يمكنك استخدام جدول مخصّص لبيانات اعتماد المفتاح العام المرتبطة بمفاتيح المرور. يمكن للمستخدم اختيار كلمة مرور واحدة فقط، ولكن يمكن أن يكون لديه عدة مفاتيح مرور، على سبيل المثال، مفتاح مرور تتم مزامنته من خلال Apple iCloud Keychain وواحد عبر "مدير كلمات المرور في 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
مفتاح مرور. يمكنك التأكّد مما إذا تم الاحتفاظ بنسخة احتياطية من مفتاح المرور من خلال فحص العلامات في "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 { credentialPublicKey, credentialID } = registrationInfo;
// Existing, signed-in user
const { user } = res.locals;
// Save the credential
await Credentials.update({
id: base64CredentialID,
publicKey: base64PublicKey,
// Optional: set the platform as a default name for the credential
// (example: "Pixel 7")
name: req.useragent.platform,
transports: response.response.transports,
passkey_user_id: user.passkey_user_id,
backed_up: registrationInfo.credentialBackedUp
});
// 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 من بيانات العميل، والتي تتوفّر على الويب بالشكل الذي يظهر به المتصفّح. يحتوي المستند على مصدر الجهة المحظورة والتحدّي وandroidPackageName
إذا كان العميل أحد تطبيقات Android. بصفتك جهة محظورة، تتيح لك قراءةclientDataJSON
الوصول إلى المعلومات التي اطّلع عليها المتصفّح في وقت تقديم طلبcreate
.response.attestationObject
يحتوي على معلومتين:attestationStatement
، وهي ليست ذات صلة ما لم تستخدم المصادقة.- بيانات "
authenticatorData
" هي البيانات التي يراها موفِّر مفتاح المرور. بصفتك جهة محظورة، تتيح لك القراءةauthenticatorData
الوصول إلى البيانات التي يعرضها مقدّم مفتاح المرور والتي يعرضها في وقت تقديم طلبcreate
.
authenticatorData
يحتوي على معلومات أساسية حول بيانات اعتماد المفتاح العام المرتبطة بمفتاح المرور الذي تم إنشاؤه حديثًا:
- بيانات اعتماد المفتاح العام نفسها، ومعرّف بيانات اعتماد فريد لها.
- رقم تعريف الجهة المحظورة المرتبطة ببيانات الاعتماد.
- علامات تصف حالة المستخدم عند إنشاء مفتاح المرور، وما إذا كان المستخدم مشاركًا أم لا (يُرجى الاطّلاع على
userVerification
). - AAGUID، الذي يحدد موفِّر مفتاح المرور. يمكن أن يكون عرض موفِّر مفتاح المرور مفيدًا للمستخدمين، خاصةً إذا كان لديهم مفتاح مرور مسجَّل لخدمتك من خلال العديد من موفِّري مفاتيح المرور.
مع أنّ authenticatorData
مضمَّنة في attestationObject
، فإنّ المعلومات التي تحتوي عليها لازمة لتنفيذ مفتاح المرور سواء استخدمت المصادقة أم لا. يُرجى العِلم أنّ authenticatorData
يتم ترميزها وتحتوي على حقول مرمّزة بتنسيق ثنائي. ستتولى المكتبة من جانب الخادم عادةً التحليل وفك الترميز. إذا كنت لا تستخدم مكتبة من جهة الخادم، ننصحك باستخدام getAuthenticatorData()
من جهة العميل لتوفير بعض التحليلات وفك ترميز بيانات العمل من جهة الخادم.
الملحق: التحقّق من استجابة التسجيل
وكجزء من التفاصيل، يتم إجراء عمليات التحقّق التالية للتحقق من استجابة التسجيل:
- تأكَّد من أنّ رقم تعريف الجهة المحظورة يتطابق مع موقعك الإلكتروني.
- تأكَّد من أنّ مصدر الطلب هو مصدر متوقَّع لموقعك الإلكتروني (عنوان URL للموقع الإلكتروني الرئيسي، تطبيق Android).
- إذا كنت تطلب التحقق من المستخدم، تأكَّد من أنّ علامة التحقق من المستخدم
authenticatorData.uv
هيtrue
. تأكَّد من أنّ علامة تواجد المستخدمauthenticatorData.up
هيtrue
، لأنّ حضور المستخدم مطلوب دائمًا لمفاتيح المرور. - تحقق من أن العميل تمكن من تقديم التحدي الذي قدمته. إذا كنت لا تستخدم المصادقة، هذا الفحص غير مهم. ومع ذلك، فإنّ تنفيذ عملية التحقّق هذه هو من أفضل الممارسات، لأنّها تضمن أنّ الرمز البرمجي جاهز إذا قررت استخدام المصادقة في المستقبل.
- تأكَّد من أنّه لم يتم تسجيل رقم تعريف بيانات الاعتماد بعد لأي مستخدم.
- تأكَّد من أنّ الخوارزمية التي يستخدمها موفِّر مفتاح المرور لإنشاء بيانات الاعتماد هي خوارزمية أدرجتها (في كل حقل
alg
منpublicKeyCredentialCreationOptions.pubKeyCredParams
، ويتم تحديدها عادةً داخل مكتبتك على جانب الخادم ولا تظهر لك). وهذا يضمن أن المستخدمين لا يمكنهم التسجيل إلا باستخدام الخوارزميات التي اخترت السماح بها.
لمزيد من المعلومات، يمكنك الاطّلاع على رمز مصدر verifyRegistrationResponse
من SimpleWebAuthn أو الاطّلاع على تفاصيل القائمة الكاملة لعمليات إثبات الملكية ضمن المواصفات.