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

نظرة عامة

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

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

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

التالي

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