1. قبل البدء
يُعدّ استخدام مفاتيح المرور بدلاً من كلمات المرور طريقة رائعة للمواقع الإلكترونية لجعل حسابات المستخدمين أكثر أمانًا وأسهل استخدامًا. باستخدام مفتاح مرور، يمكن للمستخدم تسجيل الدخول إلى موقع إلكتروني أو تطبيق من خلال ميزة قفل شاشة الجهاز، مثل بصمة الإصبع أو الوجه أو رقم التعريف الشخصي للجهاز. يجب إنشاء مفتاح مرور وربطه بحساب مستخدم وتخزين مفتاحه العام على خادم قبل أن يتمكّن المستخدم من تسجيل الدخول باستخدامه.
في هذا الدرس التطبيقي حول الترميز، ستحوّل عملية تسجيل الدخول الأساسية المستندة إلى نموذج باستخدام اسم المستخدم وكلمة المرور إلى عملية تتيح استخدام مفاتيح المرور وتتضمّن ما يلي:
- زر ينشئ مفتاح مرور بعد أن يسجّل المستخدم الدخول
- واجهة مستخدم تعرض قائمة بمفاتيح المرور المسجّلة
- نموذج تسجيل الدخول الحالي الذي يتيح للمستخدمين تسجيل الدخول باستخدام مفتاح مرور مسجّل من خلال ميزة "الملء التلقائي للنماذج"
المتطلبات الأساسية
- فهم أساسي للغة JavaScript
- فهم أساسي لمفاتيح المرور
- فهم أساسي لواجهة برمجة التطبيقات Web Authentication API (WebAuthn)
أهداف الدورة التعليمية
- كيفية إنشاء مفتاح مرور
- كيفية مصادقة المستخدمين باستخدام مفتاح مرور
- كيفية السماح لأحد النماذج باقتراح مفتاح مرور كخيار لتسجيل الدخول
المتطلبات
إحدى مجموعات الأجهزة التالية:
- متصفّح Google Chrome على جهاز Android يعمل بالإصدار 9 من نظام التشغيل Android أو إصدار أحدث، ويُفضّل أن يكون مزوّدًا بمستشعر بيومتري
- متصفّح Chrome على جهاز Windows يعمل بنظام التشغيل Windows 10 أو إصدار أحدث
- متصفّح Safari 16 أو إصدار أحدث على هاتف iPhone يعمل بالإصدار 16 من نظام التشغيل iOS أو إصدار أحدث، أو جهاز iPad يعمل بالإصدار 16 من نظام التشغيل iPadOS أو إصدار أحدث
- متصفّح Safari 16 أو إصدار أحدث أو Chrome على جهاز مكتبي من Apple يعمل بنظام التشغيل macOS Ventura أو إصدار أحدث
2. طريقة الإعداد
في هذا الدرس العملي، ستستخدم خدمة تُسمى Glitch، تتيح لك تعديل الرمز البرمجي من جهة العميل والخادم باستخدام JavaScript، ونشره من المتصفّح فقط.
افتح المشروع
- افتح المشروع في Glitch.
- انقر على إنشاء ريمكس لإنشاء نسخة من مشروع Glitch.
- في قائمة التنقّل في أسفل Glitch، انقر على معاينة > المعاينة في نافذة جديدة. سيتم فتح علامة تبويب أخرى في المتصفّح.
فحص الحالة الأولية للموقع الإلكتروني
- في علامة التبويب "معاينة"، أدخِل اسم مستخدم عشوائيًا، ثم انقر على التالي.
- أدخِل كلمة مرور عشوائية، ثم انقر على تسجيل الدخول. يتم تجاهل كلمة المرور، ولكن يتم إثبات هويتك وتنتقل إلى الصفحة الرئيسية.
- إذا أردت تغيير اسمك المعروض، يمكنك إجراء ذلك، وهذا كل ما يمكنك فعله في الحالة الأولية.
- انقُر على الخروج.
في هذه الحالة، على المستخدمين إدخال كلمة مرور في كل مرة يسجّلون فيها الدخول. يمكنك إضافة ميزة مفتاح المرور إلى هذا النموذج ليتمكّن المستخدمون من تسجيل الدخول باستخدام وظيفة قفل الشاشة على الجهاز. يمكنك تجربة الحالة النهائية على https://passkeys-codelab.glitch.me/.
لمزيد من المعلومات حول طريقة عمل مفاتيح المرور، يُرجى الاطّلاع على المقالة كيف تعمل مفاتيح المرور؟.
3- إضافة إمكانية إنشاء مفتاح مرور
للسماح للمستخدمين بالمصادقة باستخدام مفتاح مرور، يجب أن تتيح لهم إمكانية إنشاء مفتاح مرور وتسجيله وتخزين مفتاحه العام على الخادم.
عليك السماح بإنشاء مفتاح مرور بعد أن يسجّل المستخدم الدخول باستخدام كلمة مرور، وإضافة واجهة مستخدم تتيح للمستخدمين إنشاء مفتاح مرور والاطّلاع على قائمة بجميع مفاتيح المرور المسجّلة في الصفحة /home
. في القسم التالي، ستنشئ دالة تنشئ مفتاح مرور وتسجّله.
إنشاء الدالة registerCredential()
- في Glitch، انتقِل إلى الملف
public/client.js
ثم انتقِل إلى النهاية. - بعد التعليق ذي الصلة، أضِف الدالة
registerCredential()
التالية:
public/client. js
// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
// TODO: Add an ability to create a passkey: Create a credential.
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
};
تنشئ هذه الدالة مفتاح مرور وتسجّله على الخادم.
الحصول على التحدي والخيارات الأخرى من نقطة نهاية الخادم
قبل إنشاء مفتاح مرور، عليك طلب مَعلمات لتمريرها في WebAuthn من الخادم، بما في ذلك سؤال التحقّق. WebAuthn هي واجهة برمجة تطبيقات للمتصفّح تتيح للمستخدم إنشاء مفتاح مرور والمصادقة باستخدام مفتاح المرور. لحسن الحظ، لديك نقطة نهاية خادم تستجيب لمثل هذه المَعلمات في هذا الدرس العملي.
- للحصول على التحدّي وخيارات أخرى من نقطة نهاية الخادم، أضِف الرمز التالي إلى نص الدالة
registerCredential()
بعد التعليق ذي الصلة:
public/client.js
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/registerRequest');
يتضمّن مقتطف الرمز التالي خيارات نموذجية تتلقّاها من الخادم:
{
challenge: *****,
rp: {
id: "example.com",
},
user: {
id: *****,
name: "john78",
displayName: "John",
},
pubKeyCredParams: [{
alg: -7, type: "public-key"
},{
alg: -257, type: "public-key"
}],
excludeCredentials: [{
id: *****,
type: 'public-key',
transports: ['internal', 'hybrid'],
}],
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
}
}
لا يشكّل البروتوكول بين الخادم والعميل جزءًا من مواصفات WebAuthn. ومع ذلك، تم تصميم خادم هذا الدرس العملي ليعرض ملف JSON مشابهًا قدر الإمكان لقائمة PublicKeyCredentialCreationOptions
التي يتم تمريرها إلى واجهة برمجة التطبيقات navigator.credentials.create()
الخاصة بـ WebAuthn.
الجدول التالي ليس شاملاً، ولكنّه يتضمّن المَعلمات المهمة في قاموس PublicKeyCredentialCreationOptions
:
المعلّمات | الأوصاف |
تم إنشاء اختبار مصادقة من الخادم في عنصر | |
معرّف فريد للمستخدم يجب أن تكون هذه القيمة عنصر | |
يجب أن يحتوي هذا الحقل على معرّف فريد للحساب يمكن للمستخدم التعرّف عليه، مثل عنوان بريده الإلكتروني أو اسم المستخدم. ويظهر في أداة اختيار الحساب. (إذا كنت تستخدم اسم مستخدم، استخدِم القيمة نفسها المستخدَمة في مصادقة كلمة المرور). | |
هذا الحقل هو اسم اختياري سهل الاستخدام للحساب. لا يشترط أن يكون الاسم فريدًا، ويمكن أن يكون الاسم الذي اختاره المستخدم. إذا لم يكن لموقعك الإلكتروني قيمة مناسبة لتضمينها هنا، مرِّر سلسلة فارغة. قد يظهر ذلك في أداة اختيار الحساب حسب المتصفّح. | |
معرّف الجهة المعتمِدة (RP) هو نطاق. يمكن للموقع الإلكتروني تحديد نطاقه أو لاحقة قابلة للتسجيل. على سبيل المثال، إذا كان أصل RP هو https://login.example.com:1337، يمكن أن يكون معرّف RP إما | |
يحدّد هذا الحقل خوارزميات المفتاح العام المتوافقة مع الجهة المعتمدة. ننصحك بضبطه على | |
تقدّم هذه السمة قائمة بمعرّفات بيانات الاعتماد المسجّلة مسبقًا لمنع تسجيل الجهاز نفسه مرّتين. في حال توفُّرها، يجب أن يحتوي العنصر | |
اضبطها على القيمة | |
اضبطها على | |
اضبطها على القيمة |
إنشاء بيانات اعتماد
- في نص الدالة
registerCredential()
بعد التعليق ذي الصلة، حوِّل بعض المَعلمات المرمَّزة باستخدام Base64URL إلى ثنائية مرة أخرى، وتحديدًا السلسلتَينuser.id
وchallenge
، وحالات السلسلةid
المُضمَّنة في المصفوفةexcludeCredentials
:
public/client.js
// TODO: Add an ability to create a passkey: Create a credential.
// Base64URL decode some values.
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);
}
}
- في السطر التالي، اضبط
authenticatorSelection.authenticatorAttachment
على"platform"
وauthenticatorSelection.requireResidentKey
علىtrue
. يسمح ذلك باستخدام أداة مصادقة على المنصة (الجهاز نفسه) فقط مع إمكانية استخدام بيانات اعتماد قابلة للاكتشاف.
public/client.js
// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
authenticatorAttachment: 'platform',
requireResidentKey: true
}
- في السطر التالي، استدعِ طريقة
navigator.credentials.create()
لإنشاء بيانات اعتماد.
public/client.js
// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
publicKey: options,
});
من خلال هذا الطلب، يحاول المتصفّح إثبات هوية المستخدم باستخدام قفل شاشة الجهاز.
تسجيل بيانات الاعتماد في نقطة نهاية الخادم
بعد أن يثبت المستخدم هويته، يتم إنشاء مفتاح مرور وتخزينه. يتلقّى الموقع الإلكتروني عنصر بيانات اعتماد يحتوي على مفتاح عام يمكنك إرساله إلى الخادم لتسجيل مفتاح المرور.
يحتوي مقتطف الرمز التالي على مثال لكائن بيانات الاعتماد:
{
"id": *****,
"rawId": *****,
"type": "public-key",
"response": {
"clientDataJSON": *****,
"attestationObject": *****,
"transports": ["internal", "hybrid"]
},
"authenticatorAttachment": "platform"
}
الجدول التالي ليس شاملاً، ولكنّه يتضمّن المَعلمات المهمة في العنصر PublicKeyCredential
:
المعلّمات | الأوصاف |
معرّف مشفّر بنظام Base64URL لمفتاح المرور الذي تم إنشاؤه يساعد رقم التعريف هذا المتصفّح في تحديد ما إذا كان مفتاح مرور مطابقًا متوفّرًا على الجهاز عند المصادقة. يجب تخزين هذه القيمة في قاعدة البيانات على الخلفية. | |
تمثّل هذه السمة نسخة من معرّف بيانات الاعتماد بتنسيق | |
عنصر | |
كائن شهادة مشفّر | |
قائمة بوسائل النقل التي يتوافق معها الجهاز: يعني | |
تعرِض هذه السمة القيمة |
لإرسال عنصر بيانات الاعتماد إلى الخادم، اتّبِع الخطوات التالية:
- رمِّز المَعلمات الثنائية لبيانات الاعتماد باستخدام Base64URL حتى يمكن تسليمها إلى الخادم كسلسلة:
public/client.js
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
if (cred.authenticatorAttachment) {
credential.authenticatorAttachment = cred.authenticatorAttachment;
}
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const attestationObject = base64url.encode(cred.response.attestationObject);
// Obtain transports.
const transports = cred.response.getTransports ? cred.response.getTransports() : [];
credential.response = {
clientDataJSON,
attestationObject,
transports
};
- في السطر التالي، أرسِل العنصر إلى الخادم:
public/client.js
return await _fetch('/auth/registerResponse', credential);
عند تشغيل البرنامج، يعرض الخادم HTTP code 200
، ما يشير إلى أنّ بيانات الاعتماد مسجّلة.
أصبحت لديك الآن الدالة registerCredential()
الكاملة.
مراجعة رمز الحلّ لهذا القسم
public/client.js
// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from server endpoint.
const options = await _fetch('/auth/registerRequest');
// TODO: Add an ability to create a passkey: Create a credential.
// Base64URL decode some values.
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);
}
}
// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
authenticatorAttachment: 'platform',
requireResidentKey: true
}
// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
publicKey: options,
});
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
if (cred.authenticatorAttachment) {
credential.authenticatorAttachment = cred.authenticatorAttachment;
}
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const attestationObject =
base64url.encode(cred.response.attestationObject);
// Obtain transports.
const transports = cred.response.getTransports ?
cred.response.getTransports() : [];
credential.response = {
clientDataJSON,
attestationObject,
transports
};
return await _fetch('/auth/registerResponse', credential);
};
4. إنشاء واجهة مستخدم لتسجيل بيانات اعتماد مفتاح المرور وإدارتها
بعد أن أصبحت وظيفة registerCredential()
متاحة، تحتاج إلى زر لتفعيلها. عليك أيضًا عرض قائمة بمفاتيح المرور المسجّلة.
إضافة رمز HTML للعنصر النائب
- في Glitch، انتقِل إلى ملف
views/home.html
. - بعد التعليق ذي الصلة، أضِف عنصرًا نائبًا لواجهة المستخدم يعرض زرًا لتسجيل مفتاح مرور وقائمة بمفاتيح المرور:
views/home.html
<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
<h3 class="mdc-typography mdc-typography--headline6"> Your registered
passkeys:</h3>
<div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button>
العنصر div#list
هو العنصر النائب للقائمة.
التحقّق من إمكانية استخدام مفتاح المرور
لعرض خيار إنشاء مفتاح مرور للمستخدمين الذين لديهم أجهزة متوافقة مع مفاتيح المرور فقط، عليك أولاً التحقّق مما إذا كانت WebAuthn متاحة. إذا كان الأمر كذلك، عليك إزالة الفئة hidden
لعرض الزر إنشاء مفتاح مرور.
للتحقّق مما إذا كانت إحدى البيئات تتيح استخدام مفاتيح المرور، اتّبِع الخطوات التالية:
- في نهاية ملف
views/home.html
بعد التعليق ذي الصلة، اكتب شرطًا يتم تنفيذه إذا كانت قيمwindow.PublicKeyCredential
وPublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable
وPublicKeyCredential.isConditionalMediationAvailable
هيtrue
.
views/home.html
// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Feature detections
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
PublicKeyCredential.isConditionalMediationAvailable) {
- في نص الشرط، تحقَّق ممّا إذا كان الجهاز يمكنه إنشاء مفتاح مرور، ثم تحقَّق ممّا إذا كان يمكن اقتراح مفتاح المرور في عملية الملء التلقائي للنموذج.
views/home.html
try {
const results = await Promise.all([
// Is platform authenticator available in this browser?
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
// Is conditional UI available in this browser?
PublicKeyCredential.isConditionalMediationAvailable()
]);
- إذا تم استيفاء جميع الشروط، اعرض الزر لإنشاء مفتاح مرور. بخلاف ذلك، اعرض رسالة تحذير.
views/home.html
if (results.every(r => r === true)) {
// If conditional UI is available, reveal the Create a passkey button.
createPasskey.classList.remove('hidden');
} else {
// If conditional UI isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
} catch (e) {
console.error(e);
}
} else {
// If WebAuthn isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
عرض مفاتيح المرور المسجَّلة في قائمة
- حدِّد الدالة
renderCredentials()
التي تسترد مفاتيح المرور المسجّلة من الخادم وتعرضها في قائمة. لحسن الحظ، لديك نقطة نهاية الخادم/auth/getKeys
لجلب مفاتيح المرور المسجّلة للمستخدم الذي سجّل الدخول.
views/home.html
// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
const res = await _fetch('/auth/getKeys');
const list = $('#list');
const creds = html`${res.length > 0 ? html`
<mwc-list>
${res.map(cred => html`
<mwc-list-item>
<div class="list-item">
<div class="entity-name">
<span>${cred.name || 'Unnamed' }</span>
</div>
<div class="buttons">
<mwc-icon-button data-cred-id="${cred.id}"
data-name="${cred.name || 'Unnamed' }" @click="${rename}"
icon="edit"></mwc-icon-button>
<mwc-icon-button data-cred-id="${cred.id}" @click="${remove}"
icon="delete"></mwc-icon-button>
</div>
</div>
</mwc-list-item>`)}
</mwc-list>` : html`
<mwc-list>
<mwc-list-item>No credentials found.</mwc-list-item>
</mwc-list>`}`;
render(creds, list);
};
- في السطر التالي، استدعِ الدالة
renderCredentials()
لعرض مفاتيح المرور المسجّلة فور وصول المستخدم إلى الصفحة/home
كعملية تهيئة.
views/home.html
renderCredentials();
إنشاء مفتاح مرور وتسجيله
لإنشاء مفتاح مرور وتسجيله، عليك استدعاء الدالة registerCredential()
التي نفّذتها سابقًا.
لتفعيل الدالة registerCredential()
عند النقر على الزر إنشاء مفتاح مرور، اتّبِع الخطوات التالية:
- في ملف بعد عنصر نائب HTML، ابحث عن عبارة
import
التالية:
views/home.html
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
} from '/client.js';
- في نهاية نص عبارة
import
، أضِف الدالةregisterCredential()
.
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
registerCredential
} from '/client.js';
- في نهاية الملف بعد التعليق ذي الصلة، حدِّد الدالة
register()
التي تستدعي الدالةregisterCredential()
وواجهة مستخدم التحميل، واستدعِ الدالةrenderCredentials()
بعد التسجيل. يوضّح ذلك أنّ المتصفّح ينشئ مفتاح مرور ويعرض رسالة خطأ عند حدوث مشكلة.
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
try {
// Start the loading UI.
loading.start();
// Start creating a passkey.
await registerCredential();
// Stop the loading UI.
loading.stop();
// Render the updated passkey list.
renderCredentials();
- في نص الدالة
register()
، يمكنك رصد الاستثناءات. يعرض الإجراءnavigator.credentials.create()
الخطأInvalidStateError
عندما يكون مفتاح مرور متوفّرًا على الجهاز. يتم فحص ذلك باستخدام مصفوفةexcludeCredentials
. في هذه الحالة، يمكنك عرض رسالة ذات صلة للمستخدم. ويعرض أيضًا الخطأNotAllowedError
عندما يلغي المستخدم مربّع حوار المصادقة. يمكنك تجاهله في هذه الحالة.
views/home.html
} catch (e) {
// Stop the loading UI.
loading.stop();
// An InvalidStateError indicates that a passkey already exists on the device.
if (e.name === 'InvalidStateError') {
alert('A passkey already exists for this device.');
// A NotAllowedError indicates that the user canceled the operation.
} else if (e.name === 'NotAllowedError') {
Return;
// Show other errors in an alert.
} else {
alert(e.message);
console.error(e);
}
}
};
- في السطر الذي يلي الدالة
register()
، اربط الدالةregister()
بحدثclick
للزر إنشاء مفتاح مرور.
views/home.html
createPasskey.addEventListener('click', register);
مراجعة رمز الحلّ لهذا القسم
views/home.html
<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
<h3 class="mdc-typography mdc-typography--headline6"> Your registered
passkeys:</h3>
<div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button>
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
registerCredential
} from '/client.js';
views/home.html
// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Feature detections
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
PublicKeyCredential.isConditionalMediationAvailable) {
try {
const results = await Promise.all([
// Is platform authenticator available in this browser?
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
// Is conditional UI available in this browser?
PublicKeyCredential.isConditionalMediationAvailable()
]);
if (results.every(r => r === true)) {
// If conditional UI is available, reveal the Create a passkey button.
createPasskey.classList.remove('hidden');
} else {
// If conditional UI isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
} catch (e) {
console.error(e);
}
} else {
// If WebAuthn isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
const res = await _fetch('/auth/getKeys');
const list = $('#list');
const creds = html`${res.length > 0 ? html`
<mwc-list>
${res.map(cred => html`
<mwc-list-item>
<div class="list-item">
<div class="entity-name">
<span>${cred.name || 'Unnamed' }</span>
</div>
<div class="buttons">
<mwc-icon-button data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed' }" @click="${rename}" icon="edit"></mwc-icon-button>
<mwc-icon-button data-cred-id="${cred.id}" @click="${remove}" icon="delete"></mwc-icon-button>
</div>
</div>
</mwc-list-item>`)}
</mwc-list>` : html`
<mwc-list>
<mwc-list-item>No credentials found.</mwc-list-item>
</mwc-list>`}`;
render(creds, list);
};
renderCredentials();
// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
try {
// Start the loading UI.
loading.start();
// Start creating a passkey.
await registerCredential();
// Stop the loading UI.
loading.stop();
// Render the updated passkey list.
renderCredentials();
} catch (e) {
// Stop the loading UI.
loading.stop();
// An InvalidStateError indicates that a passkey already exists on the device.
if (e.name === 'InvalidStateError') {
alert('A passkey already exists for this device.');
// A NotAllowedError indicates that the user canceled the operation.
} else if (e.name === 'NotAllowedError') {
Return;
// Show other errors in an alert.
} else {
alert(e.message);
console.error(e);
}
}
};
createPasskey.addEventListener('click', register);
التجربة الآن
إذا اتّبعت جميع الخطوات حتى الآن، تكون قد نفّذت إمكانية إنشاء مفاتيح مرور وتسجيلها وعرضها على الموقع الإلكتروني.
لتجربة هذه الميزة، اتّبِع الخطوات التالية:
- في علامة التبويب "معاينة"، سجِّل الدخول باستخدام اسم مستخدم وكلمة مرور عشوائيين.
- انقر على إنشاء مفتاح مرور.
- أثبِت هويتك باستخدام قفل شاشة الجهاز.
- تأكَّد من تسجيل مفتاح مرور وظهوره ضمن قسم مفاتيح المرور المسجّلة في صفحة الويب.
إعادة تسمية مفاتيح المرور المسجّلة وإزالتها
من المفترض أن تتمكّن من إعادة تسمية مفاتيح المرور المسجّلة أو حذفها من القائمة. يمكنك الاطّلاع على طريقة عملها في الرمز البرمجي المضمّن في الدرس العملي.
في Chrome، يمكنك إزالة مفاتيح المرور المسجّلة من chrome://settings/passkeys على الكمبيوتر المكتبي أو من مدير كلمات المرور في الإعدادات على Android.
للحصول على معلومات حول كيفية إعادة تسمية مفاتيح المرور المسجّلة وإزالتها على المنصات الأخرى، يُرجى الاطّلاع على صفحات الدعم الخاصة بهذه المنصات.
5- إضافة إمكانية المصادقة باستخدام مفتاح مرور
يمكن للمستخدمين الآن إنشاء مفتاح مرور وتسجيله، وبذلك يصبحون مستعدين لاستخدامه كوسيلة للمصادقة على موقعك الإلكتروني بأمان. عليك الآن إضافة إمكانية مصادقة باستخدام مفتاح مرور إلى موقعك الإلكتروني.
إنشاء الدالة authenticate()
- في ملف
public/client.js
بعد التعليق ذي الصلة، أنشئ دالة باسمauthenticate()
تتحقّق من المستخدم محليًا ثم من الخادم:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {
// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.
// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
};
الحصول على التحدي وخيارات أخرى من نقطة نهاية الخادم
قبل أن تطلب من المستخدم المصادقة، عليك طلب مَعلمات لتمريرها في WebAuthn من الخادم، بما في ذلك سؤال التحقّق.
- في نص الدالة
authenticate()
بعد التعليق ذي الصلة، استدعِ الدالة_fetch()
لإرسال طلبPOST
إلى الخادم:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/signinRequest');
تم تصميم خادم هذا الدرس العملي ليعرض JSON مشابهًا قدر الإمكان لقائمة PublicKeyCredentialRequestOptions
التي يتم تمريرها إلى واجهة برمجة التطبيقات navigator.credentials.get()
الخاصة بـ WebAuthn. يتضمّن مقتطف الرمز التالي خيارات نموذجية من المفترض أن تتلقّاها:
{
"challenge": *****,
"rpId": "passkeys-codelab.glitch.me",
"allowCredentials": []
}
الجدول التالي ليس شاملاً، ولكنّه يتضمّن المَعلمات المهمة في قاموس PublicKeyCredentialRequestOptions
:
المعلّمات | الأوصاف |
تحدٍ من إنشاء الخادم في عنصر | |
معرّف الجهة الاعتمادية هو نطاق. يمكن للموقع الإلكتروني تحديد نطاقه أو لاحقة قابلة للتسجيل. يجب أن تتطابق هذه القيمة مع المَعلمة | |
تُستخدَم هذه السمة للعثور على أدوات مصادقة مؤهَّلة لهذه المصادقة. يمكنك تمرير مصفوفة فارغة أو تركها بدون تحديد لكي يعرض المتصفّح أداة اختيار الحساب. | |
اضبطها على القيمة |
التحقّق من هوية المستخدم محليًا والحصول على بيانات اعتماد
- في نص الدالة
authenticate()
بعد التعليق ذي الصلة، حوِّل المَعلمةchallenge
إلى ثنائية مرة أخرى:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.
// Base64URL decode the challenge.
options.challenge = base64url.decode(options.challenge);
- مرِّر مصفوفة فارغة إلى المَعلمة
allowCredentials
لفتح أداة اختيار الحساب عند مصادقة المستخدم:
public/client.js
// An empty allowCredentials array invokes an account selector by discoverable credentials.
options.allowCredentials = [];
يستخدم أداة اختيار الحساب معلومات المستخدم المخزَّنة مع مفتاح المرور.
- استدعِ طريقة
navigator.credentials.get()
مع الخيارmediation: 'conditional'
:
public/client.js
// Invoke the WebAuthn get() method.
const cred = await navigator.credentials.get({
publicKey: options,
// Request a conditional UI.
mediation: 'conditional'
});
يطلب هذا الخيار من المتصفّح اقتراح مفاتيح مرور بشكل مشروط كجزء من التعبئة التلقائية للنماذج.
التحقّق من بيانات الاعتماد
بعد أن يثبت المستخدم هويته محليًا، من المفترض أن تتلقّى عنصر بيانات اعتماد يحتوي على توقيع يمكنك التحقّق منه على الخادم.
يتضمّن مقتطف الرمز التالي مثالاً على الكائن PublicKeyCredential
:
{
"id": *****,
"rawId": *****,
"type": "public-key",
"response": {
"clientDataJSON": *****,
"authenticatorData": *****,
"signature": *****,
"userHandle": *****
},
authenticatorAttachment: "platform"
}
الجدول التالي ليس شاملاً، ولكنّه يتضمّن المَعلمات المهمة في العنصر PublicKeyCredential
:
المعلّمات | الأوصاف |
رقم التعريف المشفّر Base64URL لبيانات اعتماد مفتاح المرور التي تمّت المصادقة عليها. | |
تمثّل هذه السمة نسخة من معرّف بيانات الاعتماد بتنسيق | |
عنصر | |
عنصر | |
عنصر | |
عنصر | |
تعرض هذه السمة السلسلة |
لإرسال عنصر بيانات الاعتماد إلى الخادم، اتّبِع الخطوات التالية:
- في نص الدالة
authenticate()
بعد التعليق ذي الصلة، يجب ترميز المَعلمات الثنائية لبيانات الاعتماد حتى يمكن تسليمها إلى الخادم كسلسلة:
public/client.js
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// Base64URL encode some values.
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,
};
- أرسِل العنصر إلى الخادم:
public/client.js
return await _fetch(`/auth/signinResponse`, credential);
عند تشغيل البرنامج، يعرض الخادم HTTP code 200
، ما يشير إلى أنّه تم التحقّق من بيانات الاعتماد.
أصبحت لديك الآن الدالة الكاملة authentication()
.
مراجعة رمز الحلّ لهذا القسم
public/client.js
// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {
// TODO: Add an ability to authenticate with a passkey: Obtain the
challenge and other options from the server endpoint.
const options = await _fetch('/auth/signinRequest');
// TODO: Add an ability to authenticate with a passkey: Locally verify
the user and get a credential.
// Base64URL decode the challenge.
options.challenge = base64url.decode(options.challenge);
// The empty allowCredentials array invokes an account selector
by discoverable credentials.
options.allowCredentials = [];
// Invoke the WebAuthn get() function.
const cred = await navigator.credentials.get({
publicKey: options,
// Request a conditional UI.
mediation: 'conditional'
});
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// Base64URL encode some values.
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. إضافة مفاتيح مرور إلى ميزة "الملء التلقائي" في المتصفّح
عندما يعود المستخدم، عليك أن توفّر له إمكانية تسجيل الدخول بأسهل طريقة ممكنة وبأمان. في حال إضافة زر تسجيل الدخول باستخدام مفتاح مرور إلى صفحة تسجيل الدخول، يمكن للمستخدم الضغط على الزر واختيار مفتاح مرور في أداة اختيار الحساب في المتصفّح واستخدام قفل الشاشة لإثبات الهوية.
ومع ذلك، لا يتم نقل جميع المستخدمين من كلمات المرور إلى مفاتيح المرور في الوقت نفسه. يعني ذلك أنّه لا يمكنك إزالة كلمات المرور إلى أن ينتقل جميع المستخدمين إلى مفاتيح المرور، لذا عليك ترك نموذج تسجيل الدخول المستند إلى كلمة المرور إلى حين ذلك. مع ذلك، إذا تركت نموذج كلمة مرور وزر مفتاح مرور، سيضطر المستخدمون إلى الاختيار بينهما لتسجيل الدخول. من المفترض أن تكون عملية تسجيل الدخول بسيطة.
وهنا يأتي دور واجهة المستخدم الشرطية. واجهة المستخدم الشرطية هي إحدى ميزات WebAuthn التي تتيح لك إنشاء حقل إدخال نموذج لاقتراح مفتاح مرور كجزء من عناصر الملء التلقائي بالإضافة إلى كلمات المرور. إذا نقر المستخدم على مفتاح مرور في اقتراحات الملء التلقائي، سيُطلب منه استخدام قفل شاشة الجهاز لإثبات هويته محليًا. هذه تجربة سلسة للمستخدم لأنّ الإجراء الذي يتّخذه المستخدم مطابق تقريبًا لإجراء تسجيل الدخول باستخدام كلمة المرور.
تفعيل واجهة مستخدم شرطية
لتفعيل واجهة مستخدم شرطية، ما عليك سوى إضافة الرمز المميّز webauthn
في السمة autocomplete
لحقل إدخال. بعد ضبط الرمز المميّز، يمكنك استدعاء الطريقة navigator.credentials.get()
باستخدام السلسلة mediation: 'conditional'
لتشغيل واجهة مستخدم قفل الشاشة بشكل مشروط.
- لتفعيل واجهة مستخدم شرطية، استبدِل حقول إدخال اسم المستخدم الحالية بملف HTML التالي بعد التعليق ذي الصلة في ملف
view/index.html
:
view/index.html
<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
type="text"
id="username"
class="mdc-text-field__input"
aria-labelledby="username-label"
name="username"
autocomplete="username webauthn"
autofocus />
رصد الميزات واستدعاء WebAuthn وتفعيل واجهة مستخدم شرطية
- في ملف
view/index.html
بعد التعليق ذي الصلة، استبدِل عبارةimport
الحالية بالرمز التالي:
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
$,
_fetch,
loading,
authenticate
} from "/client.js";
يستورد هذا الرمز الدالة authenticate()
التي نفّذتها سابقًا.
- تأكَّد من توفّر العنصر
window.PulicKeyCredential
ومن أنّ الطريقةPublicKeyCredential.isConditionalMediationAvailable()
تعرض القيمةtrue
، ثم استدعِ الدالةauthenticate()
:
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
if (
window.PublicKeyCredential &&
PublicKeyCredential.isConditionalMediationAvailable
) {
try {
// Is conditional UI available in this browser?
const cma =
await PublicKeyCredential.isConditionalMediationAvailable();
if (cma) {
// If conditional UI is available, invoke the authenticate() function.
const user = await authenticate();
if (user) {
// Proceed only when authentication succeeds.
$("#username").value = user.username;
loading.start();
location.href = "/home";
} else {
throw new Error("User not found.");
}
}
} catch (e) {
loading.stop();
// A NotAllowedError indicates that the user canceled the operation.
if (e.name !== "NotAllowedError") {
console.error(e);
alert(e.message);
}
}
}
مراجعة رمز الحلّ لهذا القسم
view/index.html
<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
type="text"
id="username"
class="mdc-text-field__input"
aria-labelledby="username-label"
name="username"
autocomplete="username webauthn"
autofocus
/>
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
$,
_fetch,
loading,
authenticate
} from '/client.js';
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
// Is WebAuthn avaiable in this browser?
if (window.PublicKeyCredential &&
PublicKeyCredential.isConditionalMediationAvailable) {
try {
// Is a conditional UI available in this browser?
const cma= await PublicKeyCredential.isConditionalMediationAvailable();
if (cma) {
// If a conditional UI is available, invoke the authenticate() function.
const user = await authenticate();
if (user) {
// Proceed only when authentication succeeds.
$('#username').value = user.username;
loading.start();
location.href = '/home';
} else {
throw new Error('User not found.');
}
}
} catch (e) {
loading.stop();
// A NotAllowedError indicates that the user canceled the operation.
if (e.name !== 'NotAllowedError') {
console.error(e);
alert(e.message);
}
}
}
التجربة الآن
نفّذت عملية إنشاء مفاتيح المرور وتسجيلها وعرضها والمصادقة عليها على موقعك الإلكتروني.
لتجربة هذه الميزة، اتّبِع الخطوات التالية:
- انتقِل إلى علامة تبويب المعاينة.
- سجِّل الخروج إذا لزم الأمر.
- انقر على مربّع نص اسم المستخدم. يظهر مربّع حوار.
- اختَر الحساب الذي تريد تسجيل الدخول باستخدامه.
- أثبِت هويتك باستخدام قفل شاشة الجهاز. تتم إعادة توجيهك إلى صفحة
/home
وتسجيل الدخول.
7. تهانينا!
لقد أكملت هذا الدرس التطبيقي حول الترميز. إذا كانت لديك أي أسئلة، يمكنك طرحها على القائمة البريدية FIDO-DEV أو على StackOverflow باستخدام العلامة passkey
.