1. قبل از شروع
استفاده از کلیدهای عبور به جای گذرواژه یک راه عالی برای وب سایت ها است تا حساب های کاربری خود را ایمن تر، ساده تر و استفاده آسان تر کنند. با استفاده از کلید عبور، کاربر میتواند با استفاده از ویژگی قفل صفحه دستگاه، مانند اثر انگشت، چهره یا پین دستگاه، وارد وبسایت یا برنامهای شود. یک رمز عبور باید ایجاد شود، با یک حساب کاربری مرتبط شود و کلید عمومی آن در سرور ذخیره شود تا کاربر بتواند با آن وارد شود.
در این لبه کد، یک نام کاربری و ورود رمز عبور مبتنی بر فرم را به نامی تبدیل میکنید که از کلیدهای عبور پشتیبانی میکند و شامل موارد زیر است:
- دکمه ای که پس از ورود کاربر یک رمز عبور ایجاد می کند.
- رابط کاربری که لیستی از کلیدهای عبور ثبت شده را نمایش می دهد.
- فرم ورود به سیستم موجود که به کاربران امکان می دهد با یک کلید عبور ثبت شده از طریق تکمیل خودکار فرم وارد شوند.
پیش نیازها
- درک اولیه جاوا اسکریپت
- درک اولیه از کلیدهای عبور
- درک اولیه از Web Authentication API (WebAuthn)
چیزی که یاد خواهید گرفت
- نحوه ایجاد رمز عبور
- نحوه احراز هویت کاربران با رمز عبور
- چگونه به یک فرم اجازه دهیم یک رمز عبور را به عنوان گزینه ورود به سیستم پیشنهاد دهد.
آنچه شما نیاز دارید
یکی از ترکیب های دستگاه زیر:
- Google Chrome با یک دستگاه Android که دارای Android 9 یا بالاتر است، ترجیحاً با سنسور بیومتریک.
- Chrome با دستگاه ویندوزی که ویندوز 10 یا بالاتر را اجرا میکند.
- سافاری 16 یا بالاتر با آیفونی که iOS 16 یا بالاتر را اجرا می کند، یا آیپدی که iPadOS 16 یا بالاتر را اجرا می کند.
- Safari 16 یا بالاتر یا Chrome با دستگاه دسکتاپ Apple که دارای macOS Ventura یا بالاتر است.
2. راه اندازی شوید
در این کد لبه، شما از سرویسی به نام Glitch استفاده می کنید که به شما امکان می دهد کد مشتری و سمت سرور را با جاوا اسکریپت ویرایش کنید و آن را صرفاً از مرورگر اجرا کنید.
پروژه را باز کنید
- پروژه را در Glitch باز کنید.
- روی Remix کلیک کنید تا پروژه Glitch منقضی شود.
- در منوی پیمایش در پایین Glitch، روی Preview > Preview in a new window کلیک کنید. برگه دیگری در مرورگر شما باز می شود.
وضعیت اولیه وب سایت را بررسی کنید
- در برگه پیش نمایش، یک نام کاربری تصادفی وارد کنید و سپس روی Next کلیک کنید.
- یک رمز عبور تصادفی وارد کنید و سپس روی Sign-in کلیک کنید. رمز عبور نادیده گرفته می شود، اما شما همچنان احراز هویت هستید و در صفحه اصلی قرار می گیرید.
- اگر می خواهید نام نمایشی خود را تغییر دهید، این کار را انجام دهید. این تنها کاری است که می توانید در حالت اولیه انجام دهید.
- روی خروج کلیک کنید.
در این حالت، کاربران باید هر بار که وارد میشوند یک رمز عبور وارد کنند. شما پشتیبانی از کلید عبور را به این فرم اضافه میکنید تا کاربران بتوانند با عملکرد قفل صفحه دستگاه وارد سیستم شوند. می توانید حالت پایان را در https://passkeys-codelab.glitch.me/ امتحان کنید.
برای کسب اطلاعات بیشتر در مورد نحوه عملکرد کلیدهای عبور، به نحوه عملکرد کلیدهای عبور مراجعه کنید؟ .
3. قابلیتی برای ایجاد رمز عبور اضافه کنید
برای اینکه به کاربران اجازه احراز هویت با یک رمز عبور را بدهید، باید به آنها این امکان را بدهید که یک رمز عبور ایجاد و ثبت کنند و کلید عمومی آن را در سرور ذخیره کنند.
میخواهید پس از ورود کاربر با رمز عبور اجازه ایجاد یک رمز عبور بدهید و یک رابط کاربری اضافه کنید که به کاربران اجازه میدهد یک رمز عبور ایجاد کنند و لیستی از همه کلیدهای عبور ثبتشده را در صفحه /home
مشاهده کنند. در بخش بعدی تابعی ایجاد می کنید که یک رمز عبور ایجاد و ثبت می کند.
تابع registerCredential()
را ایجاد کنید
- در Glitch، به فایل
public/client.js
بروید و سپس به انتهای آن بروید. - پس از نظر مربوطه، تابع
registerCredential()
زیر را اضافه کنید:
عمومی / مشتری 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 یک API مرورگر است که به کاربر اجازه می دهد یک رمز عبور ایجاد کند و کاربر را با کلید عبور احراز هویت کند. خوشبختانه، شما در حال حاضر یک نقطه پایانی سرور دارید که با چنین پارامترهایی در این کد لبه پاسخ می دهد.
- برای به دست آوردن چالش و سایر گزینهها از نقطه پایانی سرور، کد زیر را پس از نظر مربوطه به بدنه تابع
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
است که به WebAuthn navigator.credentials.create()
API ارسال شده است.
جدول زیر جامع نیست، اما شامل پارامترهای مهم در فرهنگ لغت PublicKeyCredentialCreationOptions
است:
پارامترها | توضیحات |
یک چالش ایجاد شده توسط سرور در یک شی | |
شناسه منحصر به فرد یک کاربر این مقدار باید یک شی | |
این فیلد باید دارای یک شناسه منحصر به فرد برای حساب کاربری باشد که توسط کاربر قابل تشخیص است، مانند آدرس ایمیل یا نام کاربری وی. در انتخابگر حساب نمایش داده می شود. (اگر از نام کاربری استفاده می کنید، از همان مقداری که در احراز هویت رمز عبور استفاده می کنید استفاده کنید.) | |
این فیلد یک نام اختیاری و کاربرپسند برای حساب است. نیازی نیست که منحصر به فرد باشد و می تواند نام انتخابی کاربر باشد. اگر وب سایت شما مقدار مناسبی برای درج در اینجا ندارد، یک رشته خالی ارسال کنید. این ممکن است بسته به مرورگر در انتخابگر حساب نمایش داده شود. | |
شناسه حزب متکی (RP) یک دامنه است. یک وب سایت می تواند دامنه یا پسوند قابل ثبت خود را مشخص کند. به عنوان مثال، اگر مبدا یک RP https://login.example.com:1337 باشد، شناسه RP می تواند | |
این فیلد الگوریتم های کلید عمومی پشتیبانی شده 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 از کلید عبور ایجاد شده. این شناسه به مرورگر کمک میکند تا تشخیص دهد که آیا رمز عبور منطبق در دستگاه پس از احراز هویت وجود دارد یا خیر. این مقدار باید در پایگاه داده در backend ذخیره شود. | |
یک نسخه شی | |
یک شی | |
یک شیء تصدیق رمزگذاری شده | |
فهرستی از انتقالهایی که دستگاه پشتیبانی میکند: | |
هنگامی که این اعتبار در دستگاهی با قابلیت کلید عبور ایجاد شود، |
برای ارسال شی اعتبار به سرور، مراحل زیر را دنبال کنید:
- پارامترهای باینری اعتبارنامه را به صورت 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
بروید. - پس از نظر مربوطه، یک متغیر UI اضافه کنید که دکمه ای را برای ثبت رمز عبور و لیستی از کلیدهای عبور نمایش می دهد:
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
حذف کنید تا دکمه Create a Paskey نشان داده شود.
برای بررسی اینکه آیا یک محیط از کلیدهای عبور پشتیبانی می کند یا خیر، این مراحل را دنبال کنید:
- در انتهای فایل
views/home.html
بعد از نظر مربوطه، یک شرطی بنویسید که در صورتtrue
window.PublicKeyCredential
،PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable
، وPublicKeyCredential.isConditionalMediationAvailable
اجرا می شود.
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()
وقتی روی دکمه Create a Paskey کلیک میکنید، این مراحل را دنبال کنید:
- در فایل بعد از 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
برای دکمه Create a passkey متصل کنید.
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);
آن را امتحان کنید
اگر تمام مراحل را تا کنون دنبال کرده باشید، قابلیت ایجاد، ثبت و نمایش کلیدهای عبور را در وب سایت پیاده سازی کرده اید!
برای امتحان کردن، این مراحل را دنبال کنید:
- در برگه پیش نمایش، با نام کاربری و رمز عبور تصادفی وارد شوید.
- روی ایجاد رمز عبور کلیک کنید.
- هویت خود را با قفل صفحه دستگاه تأیید کنید.
- تأیید کنید که یک رمز عبور ثبت شده و در بخش رمزهای عبور ثبت شده شما در صفحه وب نمایش داده می شود.
تغییر نام و حذف رمزهای عبور ثبت شده
شما باید بتوانید رمزهای عبور ثبت شده در لیست را تغییر نام یا حذف کنید. میتوانید نحوه عملکرد آن در کد را بررسی کنید، زیرا آنها با Codelab ارائه میشوند.
در 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
است که به API navigator.credentials.get()
ارسال شده است. قطعه کد زیر شامل گزینه های نمونه ای است که باید دریافت کنید:
{
"challenge": *****,
"rpId": "passkeys-codelab.glitch.me",
"allowCredentials": []
}
جدول زیر جامع نیست، اما شامل پارامترهای مهم در فرهنگ لغت PublicKeyCredentialRequestOptions
است:
پارامترها | توضیحات |
یک چالش ایجاد شده توسط سرور در یک شی | |
شناسه RP یک دامنه است. یک وب سایت می تواند دامنه یا پسوند قابل ثبت خود را مشخص کند. این مقدار باید با پارامتر | |
این ویژگی برای یافتن احراز هویت واجد شرایط برای این احراز هویت استفاده می شود. یک آرایه خالی را ارسال کنید یا آن را نامشخص بگذارید تا مرورگر یک انتخابگر حساب را نشان دهد. | |
مقدار |
کاربر را به صورت محلی تأیید کنید و یک اعتبار دریافت کنید
- در بدنه تابع
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'
فراخوانی کنید تا به طور مشروط رابط کاربری قفل صفحه را فعال کنید.
- برای فعال کردن یک رابط کاربری شرطی، پس از نظر مربوطه در فایل
view/index.html
فیلدهای ورودی نام کاربری موجود را با 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
بپرسید.