إنشاء أول تطبيق WebAuthn

1. قبل البدء

تتيح لك واجهة برمجة تطبيقات مصادقة الويب، والمعروفة أيضًا باسم WebAuthn، إنشاء بيانات اعتماد عامة على نطاق المصدر واستخدامها لمصادقة المستخدمين.

تدعم واجهة برمجة التطبيقات استخدام أدوات المصادقة BLE وNFC وU2F أو FIDO2 التي تتجوّل عبر USB، والمعروفة أيضًا باسم مفاتيح الأمان، بالإضافة إلى برنامج مصادقة المنصة الذي يتيح للمستخدمين المصادقة باستخدام بصمات الأصابع أو أقفال الشاشة.

في هذا الدرس التطبيقي حول الترميز، أنشأت موقعًا إلكترونيًا بوظيفة إعادة مصادقة بسيطة تستخدم مستشعر بصمات إصبع. تحمي إعادة المصادقة بيانات الحساب لأنها تتطلب من المستخدمين الذين سجلوا الدخول إلى الموقع الإلكتروني إجراء المصادقة مرة أخرى عندما يحاولون إدخال أقسام مهمة من الموقع الإلكتروني أو يزورونه مرة أخرى بعد فترة زمنية معيّنة.

المتطلّبات الأساسية

  • فهم أساسيات آلية عمل WebAuthn
  • مهارات البرمجة الأساسية باستخدام JavaScript

الإجراءات التي ستنفذّها

  • إنشاء موقع إلكتروني بوظيفة إعادة مصادقة بسيطة تستخدم مستشعر بصمات الإصبع

الأشياء التي تحتاج إليها

  • أحد الأجهزة التالية:
    • أن يكون جهازًا يعمل بنظام التشغيل Android ويُفضَّل استخدام جهاز استشعار المقاييس الحيوية
    • هاتف iPhone أو جهاز iPad يتضمّن ميزة Touch ID أو Face ID على نظام التشغيل iOS 14 أو إصدار أحدث
    • جهاز MacBook Pro أو Air مزود بميزة Touch ID على الإصدار Big Sur من نظام التشغيل macOS أو إصدار أحدث
    • Windows 10 19H1 أو أحدث مع إعداد Windows Hello
  • أحد المتصفحات التالية:
    • الإصدار 67 أو الإصدارات الأحدث من Google Chrome
    • Microsoft Edge 85 أو إصدار أحدث
    • الإصدار 14 أو إصدار أحدث من Safari

2. الإعداد

في هذا الدرس التطبيقي حول الترميز، تستخدم خدمة تُسمّى glitch. هذا هو المكان الذي يمكنك من خلاله تعديل الرمز من جهة العميل والخادم باستخدام JavaScript، ونشرهما على الفور.

انتقِل إلى https://glitch.com/editmysite/webauthn-codelab-start.

الاطّلاع على طريقة العمل

اتّبِع الخطوات التالية للاطّلاع على الحالة الأولية للموقع الإلكتروني:

  1. انقر على 62bb7a6aac381af8.png عرض &gt؛ 3343769d04c09851.png في نافذة جديدة لمشاهدة الموقع الإلكتروني المباشر.
  2. أدخِل اسم مستخدم من اختيارك وانقر على التالي.
  3. أدخِل كلمة مرور وانقر على تسجيل الدخول.

يتم تجاهل كلمة المرور، ولكن لا تزال تتم مصادقتها. ستنتقل إلى الصفحة الرئيسية.

  1. انقر على تجربة إعادة المصادقة وكرِّر الخطوات الثانية والثالثة والرابعة.
  2. انقُر على الخروج.

يجب إدخال كلمة المرور في كل مرة تحاول فيها تسجيل الدخول. ويؤدي ذلك إلى محاكاة مستخدم يحتاج إلى إعادة المصادقة قبل أن يتمكن من الوصول إلى قسم مهم من أحد المواقع الإلكترونية.

إعادة مزج الرمز

  1. انتقِل إلى WebAuthn / FIDO2 API Codelab.
  2. انقر على اسم مشروعك، &gt، Remix Project 306122647ce93305.png لتسجيل المشروع ومواصلة إصدارك الخاص على عنوان URL جديد.

8d42bd24f0fd185c.png

3- تسجيل بيانات الاعتماد باستخدام بصمة الإصبع

يجب تسجيل بيانات الاعتماد التي أنشأها UVPA، وهي برنامج مصادقة مضمَّن في الجهاز ويتحقق من هوية المستخدم. ويتم اعتباره عادةً مستشعرًا لبصمة الإصبع استنادًا إلى جهاز المستخدم.

يمكنك إضافة هذه الميزة إلى الصفحة /home:

260aab9f1a2587a7.png

إنشاء دالة registerCredential()

إنشاء دالة registerCredential() تسجّل بيانات اعتماد جديدة

public/client.js

export const registerCredential = async () => {

};

الحصول على التحدي والخيارات الأخرى من نقطة نهاية الخادم

قبل أن تطلب من المستخدم تسجيل بيانات اعتماد جديدة، اطلب من معلّمات الخادم عرض معلمات لإدخال WebAuthn، بما في ذلك اختبار التحقُّق. ولحسن الحظ، لديك نقطة نهاية خادم تستجيب باستخدام هذه المعلمات.

عليك إضافة الرمز التالي إلى registerCredential().

public/client.js

const opts = {
  attestation: 'none',
  authenticatorSelection: {
    authenticatorAttachment: 'platform',
    userVerification: 'required',
    requireResidentKey: false
  }
};

const options = await _fetch('/auth/registerRequest', opts);

البروتوكول بين الخادم والعميل ليس جزءًا من مواصفات WebAuthn. ومع ذلك، تم تصميم هذا الدرس التطبيقي حول الترميز ليتناسب مع مواصفات WebAuthn وكائن JSON الذي تمرِّره إلى الخادم يشبه إلى حد كبير PublicKeyCredentialCreationOptions لكي يكون سهل الاستخدام بالنسبة إليك. يحتوي الجدول التالي على المعلمات المهمة التي يمكنك تمريرها إلى الخادم وتشرح ما تفعله:

المعلّمات

الأوصاف

attestation

الخيار المفضّل لنقل الإقرار: none أو indirect أو direct. اختر none إلا إذا كنت بحاجة إليه.

excludeCredentials

مصفوفة PublicKeyCredentialDescriptor حتى يمكن لبرنامج المصادقة تجنب إنشاء نُسخ مكررة.

authenticatorSelection

authenticatorAttachment

فلترة تطبيقات المصادقة المتاحة إذا كنت تريد تطبيق مصادقة مرتبط بالجهاز، استخدِم "platform". بالنسبة إلى مصادقات التجوال، استخدم "cross-platform".

userVerification

حدِّد ما إذا كان برنامج التحقّق من هوية المستخدم المحلي لبرنامج المصادقة هو &"required", "preferred", &"discouraged". إذا كنت تريد المصادقة ببصمة الإصبع أو قفل الشاشة, استخدِم "required".

requireResidentKey

استخدِم true في حال كانت بيانات الاعتماد التي تم إنشاؤها متاحة لمستخدِم تجربة المستخدم في المستقبل المتعلق بالحساب.

لمعرفة المزيد من المعلومات حول هذه الخيارات، يمكنك الاطّلاع على 5.4. خيارات إنشاء بيانات الاعتماد (القواميس PublicKeyCredentialCreationOptions).

وفي ما يلي أمثلة على الخيارات التي تتلقاها من الخادم.

{
  "rp": {
    "name": "WebAuthn Codelab",
    "id": "webauthn-codelab.glitch.me"
  },
  "user": {
    "displayName": "User Name",
    "id": "...",
    "name": "test"
  },
  "challenge": "...",
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }, {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "userVerification": "required"
  }
}

إنشاء بيانات اعتماد

  1. بما أنّ هذه الخيارات يتم إرسالها للتشفير عبر بروتوكول HTTP، يُرجى تحويل بعض المَعلمات إلى برنامج ثنائي، وتحديدًا user.id وchallenge ومثيلات id المضمّنة في المصفوفة excludeCredentials:

public/client.js

options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);

if (options.excludeCredentials) {
  for (let cred of options.excludeCredentials) {
    cred.id = base64url.decode(cred.id);
  }
}
  1. عليك استدعاء الطريقة navigator.credentials.create() لإنشاء بيانات اعتماد جديدة.

من خلال هذه المكالمة، يتفاعل المتصفّح مع برنامج المصادقة ويحاول إثبات هوية المستخدم باستخدام UVPA.

public/client.js

const cred = await navigator.credentials.create({
  publicKey: options,
});

بعد أن يُثبت المستخدم هويته، من المفترض أن تتلقّى كائن بيانات اعتماد يمكنك إرساله إلى الخادم وتسجيل تطبيق المصادقة.

تسجيل بيانات الاعتماد إلى نقطة نهاية الخادم

في ما يلي مثال على كائن بيانات اعتماد كان من المفترض أن تتلقّاه.

{
  "id": "...",
  "rawId": "...",
  "type": "public-key",
  "response": {
    "clientDataJSON": "...",
    "attestationObject": "..."
  }
}
  1. كما هو الحال عند تلقّي كائن خيار لتسجيل بيانات الاعتماد، عليك ترميز المَعلمات الثنائية لبيانات الاعتماد بحيث يمكن تسليمها إلى الخادم كسلسلة:

public/client.js

const credential = {};
credential.id = cred.id;
credential.rawId = base64url.encode(cred.rawId);
credential.type = cred.type;

if (cred.response) {
  const clientDataJSON =
    base64url.encode(cred.response.clientDataJSON);
  const attestationObject =
    base64url.encode(cred.response.attestationObject);
  credential.response = {
    clientDataJSON,
    attestationObject,
  };
}
  1. تخزين معرّف بيانات الاعتماد محليًا بحيث يمكنك استخدامها للمصادقة في حال عودة المستخدم:

public/client.js

localStorage.setItem(`credId`, credential.id);
  1. أرسِل الكائن إلى الخادم، وفي حال عرضه HTTP code 200، يمكنك اعتبار بيانات الاعتماد الجديدة على أنها مسجَّلة بنجاح.

public/client.js

return await _fetch('/auth/registerResponse' , credential);

لديك الآن دالة registerCredential() الكاملة.

الرمز النهائي لهذا القسم

public/client.js

...
export const registerCredential = async () => {
  const opts = {
    attestation: 'none',
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      userVerification: 'required',
      requireResidentKey: false
    }
  };

  const options = await _fetch('/auth/registerRequest', opts);

  options.user.id = base64url.decode(options.user.id);
  options.challenge = base64url.decode(options.challenge);

  if (options.excludeCredentials) {
    for (let cred of options.excludeCredentials) {
      cred.id = base64url.decode(cred.id);
    }
  }
  
  const cred = await navigator.credentials.create({
    publicKey: options
  });

  const credential = {};
  credential.id =     cred.id;
  credential.rawId =  base64url.encode(cred.rawId);
  credential.type =   cred.type;

  if (cred.response) {
    const clientDataJSON =
      base64url.encode(cred.response.clientDataJSON);
    const attestationObject =
      base64url.encode(cred.response.attestationObject);
    credential.response = {
      clientDataJSON,
      attestationObject
    };
  }

  localStorage.setItem(`credId`, credential.id);
  
  return await _fetch('/auth/registerResponse' , credential);
};
...

4. إنشاء واجهة المستخدم لتسجيل بيانات الاعتماد والحصول عليها وإزالتها

من المفيد أن تحصل على قائمة ببيانات الاعتماد والأزرار المسجلة لإزالتها.

9b5b5ae4a7b316bd.png

العنصر النائب لواجهة المستخدم

أضِف واجهة مستخدم لقائمة بيانات الاعتماد وزرًا لتسجيل بيانات اعتماد جديدة. بناءً على ما إذا كانت الميزة متوفّرة أم لا، يمكنك إزالة الفئة hidden من رسالة التحذير أو الزر لتسجيل بيانات اعتماد جديدة. ul#list هو العنصر النائب لإضافة قائمة ببيانات الاعتماد المُسجَّلة.

VIEWS/home.html

<p id="uvpa_unavailable" class="hidden">
  This device does not support User Verifying Platform Authenticator. You can't register a credential.
</p>
<h3 class="mdc-typography mdc-typography--headline6">
  Your registered credentials:
</h3>
<section>
  <div id="list"></div>
</section>
<mwc-button id="register" class="hidden" icon="fingerprint" raised>Add a credential</mwc-button>

رصد الميزات ومدى توفّر UVPA

اتّبِع الخطوات التالية للتحقّق من مدى توفّر UVPA:

  1. افحص window.PublicKeyCredential لمعرفة ما إذا كان WebAuthn متاحًا.
  2. اتصل بالرقم PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() لمعرفة ما إذا كان UVPA متوفرًا . وفي حال توفّرها، سيظهر لك الزر لتسجيل بيانات اعتماد جديدة. وفي حال عدم توفّر أي منهما، سيتم عرض رسالة التحذير.

VIEWS/home.html

const register = document.querySelector('#register');

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa) {
      register.classList.remove('hidden');
    } else {
      document
        .querySelector('#uvpa_unavailable')
        .classList.remove('hidden');
    }
  });        
} else {
  document
    .querySelector('#uvpa_unavailable')
    .classList.remove('hidden');
}

الحصول على قائمة ببيانات الاعتماد وعرضها

  1. أنشِئ دالة getCredentials() بحيث يمكنك الحصول على بيانات الاعتماد المسجّلة وعرضها في قائمة. ولحسن الحظ، لديك نقطة نهاية سهلة الاستخدام على الخادم /auth/getKeys الذي يمكنك من خلاله جلب بيانات الاعتماد المسجّلة للمستخدم الذي سجّل الدخول.

يتضمن JSON المعروض معلومات اعتماد، مثل id وpublicKey. يمكنك إنشاء رمز HTML لعرضها للمستخدمين.

VIEWS/home.html

const getCredentials = async () => {
  const res = await _fetch('/auth/getKeys');
  const list = document.querySelector('#list');
  const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
    <div class="mdc-card credential">
      <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
      <pre class="public-key">${cred.publicKey}</pre>
      <div class="mdc-card__actions">
        <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
      </div>
    </div>`) : html`
    <p>No credentials found.</p>
    `}`;
  render(creds, list);
};
  1. استدعِ getCredentials() لعرض بيانات الاعتماد المتاحة فور وصول المستخدم إلى الصفحة /home.

VIEWS/home.html

getCredentials();

إزالة بيانات الاعتماد

في قائمة بيانات الاعتماد، أضفت زرًا لإزالة كل بيانات اعتماد. يمكنك إرسال طلب إلى /auth/removeKey بالإضافة إلى معلمة طلب البحث credId لإزالتها.

public/client.js

export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
  1. إضافة unregisterCredential إلى عبارة import الحالية

VIEWS/home.html

import { _fetch, unregisterCredential } from '/client.js';
  1. أضف دالة لاستدعاءها عندما ينقر المستخدم على إزالة.

VIEWS/home.html

const removeCredential = async e => {
  try {
    await unregisterCredential(e.target.id);
    getCredentials();
  } catch (e) {
    alert(e);
  }
};

تسجيل بيانات اعتماد

يمكنك استدعاء registerCredential() لتسجيل بيانات اعتماد جديدة عندما ينقر المستخدم على إضافة بيانات اعتماد.

  1. إضافة registerCredential إلى عبارة import الحالية

VIEWS/home.html

import { _fetch, registerCredential, unregisterCredential } from '/client.js';
  1. يمكن استدعاء الدالة registerCredential() مع تحديد خيارات من أجل navigator.credentials.create().

لا تنسَ تجديد قائمة بيانات الاعتماد من خلال الاتصال بالرقم getCredentials() بعد التسجيل.

VIEWS/home.html

register.addEventListener('click', e => {
  registerCredential().then(user => {
    getCredentials();
  }).catch(e => alert(e));
});

من المفترض أن يصبح بإمكانك الآن تسجيل بيانات اعتماد جديدة وعرض معلومات عنها. يمكنك تجربتها على موقعك الإلكتروني المباشر.

الرمز النهائي لهذا القسم

VIEWS/home.html

...
      <p id="uvpa_unavailable" class="hidden">
        This device does not support User Verifying Platform Authenticator. You can't register a credential.
      </p>
      <h3 class="mdc-typography mdc-typography--headline6">
        Your registered credentials:
      </h3>
      <section>
        <div id="list"></div>
        <mwc-fab id="register" class="hidden" icon="add"></mwc-fab>
      </section>
      <mwc-button raised><a href="/reauth">Try reauth</a></mwc-button>
      <mwc-button><a href="/auth/signout">Sign out</a></mwc-button>
    </main>
    <script type="module">
      import { _fetch, registerCredential, unregisterCredential } from '/client.js';
      import { html, render } from 'https://unpkg.com/lit-html@1.0.0/lit-html.js?module';

      const register = document.querySelector('#register');

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa) {
            register.classList.remove('hidden');
          } else {
            document
              .querySelector('#uvpa_unavailable')
              .classList.remove('hidden');
          }
        });        
      } else {
        document
          .querySelector('#uvpa_unavailable')
          .classList.remove('hidden');
      }

      const getCredentials = async () => {
        const res = await _fetch('/auth/getKeys');
        const list = document.querySelector('#list');
        const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
          <div class="mdc-card credential">
            <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
            <pre class="public-key">${cred.publicKey}</pre>
            <div class="mdc-card__actions">
              <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
            </div>
          </div>`) : html`
          <p>No credentials found.</p>
          `}`;
        render(creds, list);
      };

      getCredentials();

      const removeCredential = async e => {
        try {
          await unregisterCredential(e.target.id);
          getCredentials();
        } catch (e) {
          alert(e);
        }
      };

      register.addEventListener('click', e => {
        registerCredential({
          attestation: 'none',
          authenticatorSelection: {
            authenticatorAttachment: 'platform',
            userVerification: 'required',
            requireResidentKey: false
          }
        })
        .then(user => {
          getCredentials();
        })
        .catch(e => alert(e));
      });
    </script>
...

public/client.js

...
export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
...

5. مصادقة المستخدم ببصمة الإصبع

لديك الآن بيانات اعتماد مسجّلة وجاهزة للاستخدام كطريقة لمصادقة المستخدم. والآن، يمكنك إضافة وظيفة إعادة المصادقة إلى الموقع الإلكتروني. إليك تجربة المستخدم:

عندما يصل المستخدم إلى صفحة /reauth، سيظهر له الزر مصادقة إذا كان من الممكن مصادقة المقاييس الحيوية. تبدأ المصادقة باستخدام بصمة الإصبع (UVPA) عند النقر على مصادقة بنجاح والمصادقة بنجاح ثم الانتقال إلى الصفحة /home. في حال عدم توفُّر مصادقة المقاييس الحيوية أو تعذُّر إجراء المصادقة باستخدام المقاييس الحيوية، سترجع واجهة المستخدم مرة أخرى لاستخدام نموذج كلمة المرور الحالي.

b8770c4e7475b075.png

إنشاء دالة authenticate()

يمكنك إنشاء دالة باسم authenticate()، والتي تتحقَّق من هوية المستخدم باستخدام بصمة الإصبع. يمكنك إضافة رمز JavaScript هنا:

public/client.js

export const authenticate = async () => {

};

الحصول على التحدي والخيارات الأخرى من نقطة نهاية الخادم

  1. قبل المصادقة، تحقق مما إذا كان للمستخدم معرّف بيانات اعتماد مخزّن واضبطه كمعلمة طلب بحث.

عند تقديم رقم تعريف بيانات اعتماد إلى جانب خيارات أخرى، يمكن للخادم تقديم allowCredentials ذي صلة ما يجعل عملية إثبات هوية المستخدم موثوقًا بها.

public/client.js

const opts = {};

let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
  url += `?credId=${encodeURIComponent(credId)}`;
}
  1. قبل أن تطلب من المستخدم المصادقة، اطلب من الخادم إعادة إرسال اختبار تحقق وغيره من المعلمات. يمكنك استدعاء الدالة _fetch() باستخدام opts كوسيطة لإرسال طلب POST إلى الخادم.

public/client.js

const options = await _fetch(url, opts);

في ما يلي أمثلة على الخيارات التي يجب أن تتلقاها (توافق مع PublicKeyCredentialRequestOptions).

{
  "challenge": "...",
  "timeout": 1800000,
  "rpId": "webauthn-codelab.glitch.me",
  "userVerification": "required",
  "allowCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ]
}

أهم خيار هو allowCredentials. عندما تتلقّى خيارات من الخادم، يجب أن تكون allowCredentials كائنًا واحدًا في مصفوفة أو مصفوفة فارغة بناءً على ما إذا تم العثور على بيانات اعتماد تحمل رقم التعريف في معلّمة طلب البحث من جهة الخادم.

  1. اتّبع بعد null الإجراء التالي عندما تكون allowCredentials مصفوفة فارغة بحيث تعود واجهة المستخدم إلى طلب كلمة المرور.
if (options.allowCredentials.length === 0) {
  console.info('No registered credentials found.');
  return Promise.resolve(null);
}

التحقق من المستخدم محليًا والحصول على بيانات الاعتماد

  1. بما أنّ هذه الخيارات يتم إرسالها للتشفير الذي يحدث خلال بروتوكول HTTP، يمكنك تحويل بعض المعلّمات مرة أخرى إلى برنامج ثنائي، وبالأخص challenge ومثيلات id المضمّنة في المصفوفة allowCredentials:

public/client.js

options.challenge = base64url.decode(options.challenge);

for (let cred of options.allowCredentials) {
  cred.id = base64url.decode(cred.id);
}
  1. اتّبِع طريقة navigator.credentials.get() لإثبات هوية المستخدم باستخدام UVPA.

public/client.js

const cred = await navigator.credentials.get({
  publicKey: options
});

بعد أن يُثبت المستخدم هويته، من المفترض أن تتلقّى كائن بيانات اعتماد يمكنك إرساله إلى الخادم ومصادقة المستخدم.

التحقق من بيانات الاعتماد

في ما يلي مثال على عنصر PublicKeyCredential (response هو AuthenticatorAssertionResponse) كان من المفترض أن تتلقّاه:

{
  "id": "...",
  "type": "public-key",
  "rawId": "...",
  "response": {
    "clientDataJSON": "...",
    "authenticatorData": "...",
    "signature": "...",
    "userHandle": ""
  }
}
  1. يمكنك ترميز المعلمات الثنائية لبيانات الاعتماد بحيث يمكن تسليمها إلى الخادم كسلسلة:

public/client.js

const credential = {};
credential.id = cred.id;
credential.type = cred.type;
credential.rawId = base64url.encode(cred.rawId);

if (cred.response) {
  const clientDataJSON =
    base64url.encode(cred.response.clientDataJSON);
  const authenticatorData =
    base64url.encode(cred.response.authenticatorData);
  const signature =
    base64url.encode(cred.response.signature);
  const userHandle =
    base64url.encode(cred.response.userHandle);
  credential.response = {
    clientDataJSON,
    authenticatorData,
    signature,
    userHandle,
  };
}
  1. أرسِل الكائن إلى الخادم، وإذا عرَض HTTP code 200، يمكنك اعتبار المستخدم على أنه مسجّل الدخول بنجاح:

public/client.js

return await _fetch(`/auth/signinResponse`, credential);

لديك الآن دالة authentication() الكاملة.

الرمز النهائي لهذا القسم

public/client.js

...
export const authenticate = async () => {
  const opts = {};

  let url = '/auth/signinRequest';
  const credId = localStorage.getItem(`credId`);
  if (credId) {
    url += `?credId=${encodeURIComponent(credId)}`;
  }
  
  const options = await _fetch(url, opts);
  
  if (options.allowCredentials.length === 0) {
    console.info('No registered credentials found.');
    return Promise.resolve(null);
  }

  options.challenge = base64url.decode(options.challenge);

  for (let cred of options.allowCredentials) {
    cred.id = base64url.decode(cred.id);
  }

  const cred = await navigator.credentials.get({
    publicKey: options
  });

  const credential = {};
  credential.id = cred.id;
  credential.type = cred.type;
  credential.rawId = base64url.encode(cred.rawId);

  if (cred.response) {
    const clientDataJSON =
      base64url.encode(cred.response.clientDataJSON);
    const authenticatorData =
      base64url.encode(cred.response.authenticatorData);
    const signature =
      base64url.encode(cred.response.signature);
    const userHandle =
      base64url.encode(cred.response.userHandle);
    credential.response = {
      clientDataJSON,
      authenticatorData,
      signature,
      userHandle,
    };
  }

  return await _fetch(`/auth/signinResponse`, credential);
};
...

6- تفعيل تجربة إعادة المصادقة

واجهة مستخدم الإصدار

عندما يعود المستخدم مرة أخرى، فإنك تريد منه إعادة المصادقة على الموقع بسهولة وأمان. هذا هو المكان الذي تتألق فيه مصادقة المقاييس الحيوية. في بعض الحالات، قد لا تعمل مصادقة المقاييس الحيوية:

  • UVPA غير متاح.
  • لم يسجّل المستخدم أي بيانات اعتماد على جهازه حتى الآن.
  • يتم محو وحدة التخزين ولن يتذكّر الجهاز رقم تعريف بيانات الاعتماد بعد ذلك.
  • يتعذّر على المستخدم إثبات هويته لسبب ما، مثل عندما يكون إصبعه مبتلاً أو أنه يرتدي قناعًا.

ولهذا السبب، يُعدّ دائمًا توفير خيارات تسجيل دخول أخرى بمثابة إجراءات احتياطية. في هذا الدرس التطبيقي حول الترميز، يمكنك استخدام حل كلمات المرور المستنِد إلى النموذج.

19da999b0145054.png

  1. أضِف واجهة مستخدم لعرض زر المصادقة الذي يستدعي مصادقة المقاييس الحيوية بالإضافة إلى نموذج كلمة المرور.

استخدم الفئة hidden لإظهار إحداها وإخفاؤها بشكل انتقائي بناءً على حالة المستخدم.

VIEWS/reauth.html

<div id="uvpa_available" class="hidden">
  <h2>
    Verify your identity
  </h2>
  <div>
    <mwc-button id="reauth" raised>Authenticate</mwc-button>
  </div>
  <div>
    <mwc-button id="cancel">Sign-in with password</mwc-button>
  </div>
</div>
  1. إلحاق class="hidden" بالنموذج:

VIEWS/reauth.html

<form id="form" method="POST" action="/auth/password" class="hidden">

رصد الميزات ومدى توفّر UVPA

يجب على المستخدمين تسجيل الدخول باستخدام كلمة مرور في حال استيفاء أحد الشروط التالية:

  • WebAuthn غير متاح.
  • UVPA غير متاح.
  • لا يمكن اكتشاف رقم تعريف بيانات اعتماد لاختبار UVPA هذا.

يمكنك عرض زر المصادقة أو إخفائه بشكلٍ انتقائي:

VIEWS/reauth.html

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa && localStorage.getItem(`credId`)) {
      document
        .querySelector('#uvpa_available')
        .classList.remove('hidden');
    } else {
      form.classList.remove('hidden');
    }
  });        
} else {
  form.classList.remove('hidden');
}

الإجراء الاحتياطي لنموذج كلمة المرور

كما يجب أن يتمكّن المستخدم من اختيار تسجيل الدخول باستخدام كلمة مرور.

عرض نموذج كلمة المرور وإخفاء زر المصادقة عندما ينقر المستخدم على تسجيل الدخول باستخدام كلمة المرور:.

VIEWS/reauth.html

const cancel = document.querySelector('#cancel');
cancel.addEventListener('click', e => {
  form.classList.remove('hidden');
  document
    .querySelector('#uvpa_available')
    .classList.add('hidden');
});

c4a82800889f078c.png

استدعاء مصادقة المقاييس الحيوية

وأخيرًا، عليك تفعيل مصادقة المقاييس الحيوية.

  1. إضافة authenticate إلى عبارة import الحالية:

VIEWS/reauth.html

import { _fetch, authenticate } from '/client.js';
  1. استدعاء authenticate() عندما ينقر المستخدم على مصادقة لبدء مصادقة المقاييس الحيوية.

تأكّد من أن سبب الفشل في مصادقة المقاييس الحيوية يعود إلى نموذج كلمة المرور.

VIEWS/reauth.html

const button = document.querySelector('#reauth');
button.addEventListener('click', e => {
  authenticate().then(user => {
    if (user) {
      location.href = '/home';
    } else {
      throw 'User not found.';
    }
  }).catch(e => {
    console.error(e.message || e);
    alert('Authentication failed. Use password to sign-in.');
    form.classList.remove('hidden');
    document.querySelector('#uvpa_available').classList.add('hidden');
  });        
});

الرمز النهائي لهذا القسم

VIEWS/reauth.html

...
    <main class="content">
      <div id="uvpa_available" class="hidden">
        <h2>
          Verify your identity
        </h2>
        <div>
          <mwc-button id="reauth" raised>Authenticate</mwc-button>
        </div>
        <div>
          <mwc-button id="cancel">Sign-in with password</mwc-button>
        </div>
      </div>
      <form id="form" method="POST" action="/auth/password" class="hidden">
        <h2>
          Enter a password
        </h2>
        <input type="hidden" name="username" value="{{username}}" />
        <div class="mdc-text-field mdc-text-field--filled">
          <span class="mdc-text-field__ripple"></span>
          <label class="mdc-floating-label" id="password-label">password</label>
          <input type="password" class="mdc-text-field__input" aria-labelledby="password-label" name="password" />
          <span class="mdc-line-ripple"></span>
        </div>
        <input type="submit" class="mdc-button mdc-button--raised" value="Sign-In" />
        <p class="instructions">password will be ignored in this demo.</p>
      </form>
    </main>
    <script src="https://unpkg.com/material-components-web@7.0.0/dist/material-components-web.min.js"></script>
    <script type="module">
      new mdc.textField.MDCTextField(document.querySelector('.mdc-text-field'));
      import { _fetch, authenticate } from '/client.js';
      const form = document.querySelector('#form');
      form.addEventListener('submit', e => {
        e.preventDefault();
        const form = new FormData(e.target);
        const cred = {};
        form.forEach((v, k) => cred[k] = v);
        _fetch(e.target.action, cred)
        .then(user => {
          location.href = '/home';
        })
        .catch(e => alert(e));
      });

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa && localStorage.getItem(`credId`)) {
            document
              .querySelector('#uvpa_available')
              .classList.remove('hidden');
          } else {
            form.classList.remove('hidden');
          }
        });        
      } else {
        form.classList.remove('hidden');
      }

      const cancel = document.querySelector('#cancel');
      cancel.addEventListener('click', e => {
        form.classList.remove('hidden');
        document
          .querySelector('#uvpa_available')
          .classList.add('hidden');
      });

      const button = document.querySelector('#reauth');
      button.addEventListener('click', e => {
        authenticate().then(user => {
          if (user) {
            location.href = '/home';
          } else {
            throw 'User not found.';
          }
        }).catch(e => {
          console.error(e.message || e);
          alert('Authentication failed. Use password to sign-in.');
          form.classList.remove('hidden');
          document.querySelector('#uvpa_available').classList.add('hidden');
        });        
      });
    </script>
...

7- تهانينا.

لقد أنهيت هذا الدرس التطبيقي حول الترميز.

مزيد من المعلومات

شكر خاص لـيوري أكرمان من FIDO Alliance على مساعدتك.