تسجيل مفتاح المرور من جهة الخادم

نظرة عامة

في ما يلي نظرة عامة عالية المستوى على الخطوات الأساسية لتسجيل مفتاح المرور:

مسار تسجيل مفتاح المرور

  • حدِّد خيارات إنشاء مفتاح مرور. عليك إرسالها إلى العميل، لتتمكّن من تمريرها إلى طلب إنشاء مفتاح المرور: اتصال 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 الذي أرسله الخادم
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 الذي أرسله الخادم
تعرض 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 أو الاطّلاع على تفاصيل القائمة الكاملة لعمليات إثبات الملكية ضمن المواصفات.

التالي

مصادقة مفتاح المرور من جهة الخادم