کلیدهای عبور را با تکمیل خودکار فرم در یک برنامه وب پیاده سازی کنید

1. قبل از شروع

استفاده از کلیدهای عبور به جای گذرواژه یک راه عالی برای وب سایت ها است تا حساب های کاربری خود را ایمن تر، ساده تر و استفاده آسان تر کنند. با استفاده از کلید عبور، کاربر می‌تواند با استفاده از ویژگی قفل صفحه دستگاه، مانند اثر انگشت، چهره یا پین دستگاه، وارد وب‌سایت یا برنامه‌ای شود. یک رمز عبور باید ایجاد شود، با یک حساب کاربری مرتبط شود و کلید عمومی آن در سرور ذخیره شود تا کاربر بتواند با آن وارد شود.

در این لبه کد، یک نام کاربری و ورود رمز عبور مبتنی بر فرم را به نامی تبدیل می‌کنید که از کلیدهای عبور پشتیبانی می‌کند و شامل موارد زیر است:

  • دکمه ای که پس از ورود کاربر یک رمز عبور ایجاد می کند.
  • رابط کاربری که لیستی از کلیدهای عبور ثبت شده را نمایش می دهد.
  • فرم ورود به سیستم موجود که به کاربران امکان می دهد با یک کلید عبور ثبت شده از طریق تکمیل خودکار فرم وارد شوند.

پیش نیازها

چیزی که یاد خواهید گرفت

  • نحوه ایجاد رمز عبور
  • نحوه احراز هویت کاربران با رمز عبور
  • چگونه به یک فرم اجازه دهیم یک رمز عبور را به عنوان گزینه ورود به سیستم پیشنهاد دهد.

آنچه شما نیاز دارید

یکی از ترکیب های دستگاه زیر:

  • Google Chrome با یک دستگاه Android که دارای Android 9 یا بالاتر است، ترجیحاً با سنسور بیومتریک.
  • Chrome با دستگاه ویندوزی که ویندوز 10 یا بالاتر را اجرا می‌کند.
  • سافاری 16 یا بالاتر با آیفونی که iOS 16 یا بالاتر را اجرا می کند، یا آیپدی که iPadOS 16 یا بالاتر را اجرا می کند.
  • Safari 16 یا بالاتر یا Chrome با دستگاه دسکتاپ Apple که دارای macOS Ventura یا بالاتر است.

2. راه اندازی شوید

در این کد لبه، شما از سرویسی به نام Glitch استفاده می کنید که به شما امکان می دهد کد مشتری و سمت سرور را با جاوا اسکریپت ویرایش کنید و آن را صرفاً از مرورگر اجرا کنید.

پروژه را باز کنید

  1. پروژه را در Glitch باز کنید.
  2. روی Remix کلیک کنید تا پروژه Glitch منقضی شود.
  3. در منوی پیمایش در پایین Glitch، روی Preview > Preview in a new window کلیک کنید. برگه دیگری در مرورگر شما باز می شود.

دکمه Preview in a new window در منوی پیمایش در پایین Glitch

وضعیت اولیه وب سایت را بررسی کنید

  1. در برگه پیش نمایش، یک نام کاربری تصادفی وارد کنید و سپس روی Next کلیک کنید.
  2. یک رمز عبور تصادفی وارد کنید و سپس روی Sign-in کلیک کنید. رمز عبور نادیده گرفته می شود، اما شما همچنان احراز هویت هستید و در صفحه اصلی قرار می گیرید.
  3. اگر می خواهید نام نمایشی خود را تغییر دهید، این کار را انجام دهید. این تنها کاری است که می توانید در حالت اولیه انجام دهید.
  4. روی خروج کلیک کنید.

در این حالت، کاربران باید هر بار که وارد می‌شوند یک رمز عبور وارد کنند. شما پشتیبانی از کلید عبور را به این فرم اضافه می‌کنید تا کاربران بتوانند با عملکرد قفل صفحه دستگاه وارد سیستم شوند. می توانید حالت پایان را در https://passkeys-codelab.glitch.me/ امتحان کنید.

برای کسب اطلاعات بیشتر در مورد نحوه عملکرد کلیدهای عبور، به نحوه عملکرد کلیدهای عبور مراجعه کنید؟ .

3. قابلیتی برای ایجاد رمز عبور اضافه کنید

برای اینکه به کاربران اجازه احراز هویت با یک رمز عبور را بدهید، باید به آنها این امکان را بدهید که یک رمز عبور ایجاد و ثبت کنند و کلید عمومی آن را در سرور ذخیره کنند.

پس از ایجاد رمز عبور، یک گفتگوی تأیید کاربر کلید عبور ظاهر می شود.

می‌خواهید پس از ورود کاربر با رمز عبور اجازه ایجاد یک رمز عبور بدهید و یک رابط کاربری اضافه کنید که به کاربران اجازه می‌دهد یک رمز عبور ایجاد کنند و لیستی از همه کلیدهای عبور ثبت‌شده را در صفحه /home مشاهده کنند. در بخش بعدی تابعی ایجاد می کنید که یک رمز عبور ایجاد و ثبت می کند.

تابع registerCredential() را ایجاد کنید

  1. در Glitch، به فایل public/client.js بروید و سپس به انتهای آن بروید.
  2. پس از نظر مربوطه، تابع 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 است:

پارامترها

توضیحات

challenge

یک چالش ایجاد شده توسط سرور در یک شی ArrayBuffer برای این ثبت. این مورد ضروری است اما در طول ثبت نام استفاده نمی شود، مگر اینکه گواهینامه انجام دهید - موضوعی پیشرفته که در این آزمایشگاه کد پوشش داده نشده است.

user.id

شناسه منحصر به فرد یک کاربر این مقدار باید یک شی ArrayBuffer باشد که شامل اطلاعات هویتی شخصی، مانند آدرس‌های ایمیل یا نام‌های کاربری نباشد. یک مقدار تصادفی 16 بایتی ایجاد شده در هر حساب به خوبی کار می کند.

user.name

این فیلد باید دارای یک شناسه منحصر به فرد برای حساب کاربری باشد که توسط کاربر قابل تشخیص است، مانند آدرس ایمیل یا نام کاربری وی. در انتخابگر حساب نمایش داده می شود. (اگر از نام کاربری استفاده می کنید، از همان مقداری که در احراز هویت رمز عبور استفاده می کنید استفاده کنید.)

user.displayName

این فیلد یک نام اختیاری و کاربرپسند برای حساب است. نیازی نیست که منحصر به فرد باشد و می تواند نام انتخابی کاربر باشد. اگر وب سایت شما مقدار مناسبی برای درج در اینجا ندارد، یک رشته خالی ارسال کنید. این ممکن است بسته به مرورگر در انتخابگر حساب نمایش داده شود.

rp.id

شناسه حزب متکی (RP) یک دامنه است. یک وب سایت می تواند دامنه یا پسوند قابل ثبت خود را مشخص کند. به عنوان مثال، اگر مبدا یک RP https://login.example.com:1337 باشد، شناسه RP می تواند login.example.com یا example.com باشد. اگر شناسه RP به‌عنوان example.com مشخص شده باشد، کاربر می‌تواند در login.example.com یا هر زیردامنه دیگری از example.com احراز هویت کند.

pubKeyCredParams

این فیلد الگوریتم های کلید عمومی پشتیبانی شده RP را مشخص می کند. توصیه می کنیم آن را روی [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}] تنظیم کنید. این پشتیبانی از ECDSA با P-256 و RSA PKCS#1 را مشخص می‌کند و پشتیبانی از آنها پوشش کاملی را ارائه می‌دهد.

excludeCredentials

فهرستی از شناسه‌های اعتبار قبلاً ثبت‌شده را برای جلوگیری از ثبت دوبار یک دستگاه ارائه می‌کند. در صورت ارائه، عضو transports باید حاوی نتیجه فراخوانی تابع getTransports() در طول ثبت هر اعتبار باشد.

authenticatorSelection.authenticatorAttachment

مقدار "platform" را تنظیم کنید. این نشان می دهد که شما یک احراز هویت می خواهید که در دستگاه پلت فرم تعبیه شده باشد تا از کاربر خواسته نشود چیزی مانند کلید امنیتی USB را وارد کند.

authenticatorSelection.requireResidentKey

مقدار true بولی را تنظیم کنید. یک اعتبار قابل کشف (کلید ساکن) را می توان بدون نیاز به ارائه شناسه اعتبار توسط سرور استفاده کرد و بنابراین با تکمیل خودکار سازگار است.

authenticatorSelection.userVerification

مقدار "preferred" را تنظیم کنید یا آن را حذف کنید زیرا مقدار پیش فرض است. این نشان می‌دهد که آیا تأیید کاربری که از قفل صفحه دستگاه استفاده می‌کند، "required" ، "preferred" یا "discouraged" است. تنظیم روی یک مقدار "preferred" درخواست تأیید کاربر در زمانی که دستگاه توانایی دارد.

اعتبارنامه ایجاد کنید

  1. در بدنه تابع 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);
  }
}
  1. در خط بعدی، authenticatorSelection.authenticatorAttachment را روی "platform" و authenticatorSelection.requireResidentKey را روی true قرار دهید. این تنها امکان استفاده از یک تصدیق‌کننده پلتفرم (خود دستگاه) با قابلیت شناسایی قابل کشف را می‌دهد.

public/client.js

// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
  authenticatorAttachment: 'platform',
  requireResidentKey: true
}
  1. در خط بعدی، متد 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 است:

پارامترها

توضیحات

id

شناسه رمزگذاری شده Base64URL از کلید عبور ایجاد شده. این شناسه به مرورگر کمک می‌کند تا تشخیص دهد که آیا رمز عبور منطبق در دستگاه پس از احراز هویت وجود دارد یا خیر. این مقدار باید در پایگاه داده در backend ذخیره شود.

rawId

یک نسخه شی ArrayBuffer شناسه اعتبار.

response.clientDataJSON

یک شی ArrayBuffer داده های مشتری را رمزگذاری می کند.

response.attestationObject

یک شیء تصدیق رمزگذاری شده ArrayBuffer . حاوی اطلاعات مهمی مانند شناسه RP، پرچم‌ها و کلید عمومی است.

response.transports

فهرستی از انتقال‌هایی که دستگاه پشتیبانی می‌کند: "internal" به این معنی است که دستگاه از یک کلید عبور پشتیبانی می‌کند. "hybrid" به این معنی است که از احراز هویت در دستگاه دیگری نیز پشتیبانی می کند.

authenticatorAttachment

هنگامی که این اعتبار در دستگاهی با قابلیت کلید عبور ایجاد شود، "platform" را برمی‌گرداند.

برای ارسال شی اعتبار به سرور، مراحل زیر را دنبال کنید:

  1. پارامترهای باینری اعتبارنامه را به صورت 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
};
  1. در خط بعدی، شی را به سرور ارسال کنید:

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() در دسترس است، برای فراخوانی آن به یک دکمه نیاز دارید. همچنین، باید فهرستی از کلیدهای عبور ثبت شده را نمایش دهید.

کلیدهای عبور ثبت شده در صفحه /home فهرست شده است

HTML متغیری را اضافه کنید

  1. در Glitch، به فایل views/home.html بروید.
  2. پس از نظر مربوطه، یک متغیر 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 نشان داده شود.

برای بررسی اینکه آیا یک محیط از کلیدهای عبور پشتیبانی می کند یا خیر، این مراحل را دنبال کنید:

  1. در انتهای فایل 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) {
  1. در بدنه شرطی، بررسی کنید که آیا دستگاه می‌تواند یک رمز عبور ایجاد کند یا خیر و سپس بررسی کنید که آیا می‌توان کلید عبور را در تکمیل خودکار فرم پیشنهاد کرد یا خیر.

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()
  ]);
  1. اگر همه شرایط وجود دارد، دکمه ایجاد یک رمز عبور را نشان دهید. در غیر این صورت، یک پیام هشدار نشان دهید.

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.';
}

کلیدهای عبور ثبت شده را در یک لیست ارائه دهید

  1. یک تابع 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);
};
  1. در خط بعدی، تابع renderCredentials() را فراخوانی کنید تا کلیدهای عبور ثبت شده را به محض اینکه کاربر در صفحه /home به عنوان مقداردهی اولیه قرار می گیرد، نمایش دهد.

views/home.html

renderCredentials();

یک رمز عبور ایجاد و ثبت کنید

برای ایجاد و ثبت یک رمز عبور، باید تابع registerCredential() را که قبلا پیاده سازی کرده بودید فراخوانی کنید.

برای فعال کردن تابع registerCredential() وقتی روی دکمه Create a Paskey کلیک می‌کنید، این مراحل را دنبال کنید:

  1. در فایل بعد از HTML، عبارت import زیر را پیدا کنید:

views/home.html

import { 
  $, 
  _fetch, 
  loading, 
  updateCredential, 
  unregisterCredential, 
} from '/client.js';
  1. در انتهای متن دستور 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';
  1. در انتهای فایل پس از کامنت مربوطه، یک تابع 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();
  1. در بدنه تابع 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);
    }
  }
};
  1. در خط بعد از تابع 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);

آن را امتحان کنید

اگر تمام مراحل را تا کنون دنبال کرده باشید، قابلیت ایجاد، ثبت و نمایش کلیدهای عبور را در وب سایت پیاده سازی کرده اید!

برای امتحان کردن، این مراحل را دنبال کنید:

  1. در برگه پیش نمایش، با نام کاربری و رمز عبور تصادفی وارد شوید.
  2. روی ایجاد رمز عبور کلیک کنید.
  3. هویت خود را با قفل صفحه دستگاه تأیید کنید.
  4. تأیید کنید که یک رمز عبور ثبت شده و در بخش رمزهای عبور ثبت شده شما در صفحه وب نمایش داده می شود.

کلیدهای عبور ثبت شده در صفحه /home فهرست شده است.

تغییر نام و حذف رمزهای عبور ثبت شده

شما باید بتوانید رمزهای عبور ثبت شده در لیست را تغییر نام یا حذف کنید. می‌توانید نحوه عملکرد آن در کد را بررسی کنید، زیرا آنها با 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 است:

پارامترها

توضیحات

challenge

یک چالش ایجاد شده توسط سرور در یک شی ArrayBuffer . این برای جلوگیری از حملات تکراری لازم است. هرگز یک چالش را در یک پاسخ دو بار قبول نکنید. آن را یک توکن CSRF در نظر بگیرید.

rpId

شناسه RP یک دامنه است. یک وب سایت می تواند دامنه یا پسوند قابل ثبت خود را مشخص کند. این مقدار باید با پارامتر rp.id مورد استفاده در هنگام ایجاد رمز عبور مطابقت داشته باشد.

allowCredentials

این ویژگی برای یافتن احراز هویت واجد شرایط برای این احراز هویت استفاده می شود. یک آرایه خالی را ارسال کنید یا آن را نامشخص بگذارید تا مرورگر یک انتخابگر حساب را نشان دهد.

userVerification

مقدار "preferred" را تنظیم کنید یا آن را حذف کنید زیرا مقدار پیش فرض است. این نشان می‌دهد که آیا تأیید کاربر با استفاده از قفل صفحه دستگاه "required" ، "preferred" یا "discouraged" است. تنظیم روی یک مقدار "preferred" درخواست تأیید کاربر در زمانی که دستگاه توانایی دارد.

کاربر را به صورت محلی تأیید کنید و یک اعتبار دریافت کنید

  1. در بدنه تابع 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);
  1. یک آرایه خالی را به پارامتر allowCredentials ارسال کنید تا هنگام احراز هویت کاربر، یک انتخابگر حساب باز شود:

public/client.js

// An empty allowCredentials array invokes an account selector by discoverable credentials.
options.allowCredentials = [];

انتخابگر حساب از اطلاعات کاربر ذخیره شده با کلید عبور استفاده می کند.

  1. متد 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 است:

پارامترها

توضیحات

id

شناسه رمزگذاری شده Base64URL اعتبار کلید عبور تأیید شده.

rawId

یک نسخه شی ArrayBuffer شناسه اعتبار.

response.clientDataJSON

یک شی ArrayBuffer از داده های مشتری. این فیلد حاوی اطلاعاتی مانند چالش و مبدا است که سرور RP باید تأیید کند.

response.authenticatorData

یک شی ArrayBuffer از داده های احراز هویت. این فیلد حاوی اطلاعاتی مانند RP ID است.

response.signature

یک شی ArrayBuffer از امضا. این مقدار هسته اعتبارنامه است و باید در سرور تأیید شود.

response.userHandle

یک شی ArrayBuffer که شامل شناسه کاربری تنظیم شده در زمان ایجاد است. اگر سرور باید مقادیر شناسه ای را که استفاده می کند انتخاب کند، یا اگر backend بخواهد از ایجاد نمایه در شناسه های اعتبارنامه جلوگیری کند، می توان از این مقدار به جای شناسه اعتبار استفاده کرد.

authenticatorAttachment

زمانی که این اعتبار از دستگاه محلی می‌آید، رشته "platform" را برمی‌گرداند. در غیر این صورت، یک رشته "cross-platform" را برمی گرداند، به ویژه زمانی که کاربر از تلفن برای ورود به سیستم استفاده می کند . اگر کاربر نیاز به استفاده از تلفن برای ورود به سیستم دارد، از او بخواهید یک کلید عبور در دستگاه محلی ایجاد کند.

برای ارسال شی اعتبار به سرور، مراحل زیر را دنبال کنید:

  1. در بدنه تابع 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,
};
  1. ارسال شی به سرور:

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 را فراخوانی کنید و یک رابط کاربری شرطی را فعال کنید

  1. در فایل 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() را که قبلا پیاده سازی کرده اید وارد می کند.

  1. تأیید کنید که شی 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);
    }
  }
}

آن را امتحان کنید

شما ایجاد، ثبت، نمایش و احراز هویت رمزهای عبور را در وب سایت خود پیاده سازی کردید.

برای امتحان کردن، این مراحل را دنبال کنید:

  1. به برگه پیش نمایش بروید.
  2. در صورت لزوم، از سیستم خارج شوید.
  3. روی کادر متنی نام کاربری کلیک کنید. یک گفتگو ظاهر می شود.
  4. حسابی را که می خواهید با آن وارد شوید انتخاب کنید.
  5. هویت خود را با قفل صفحه دستگاه تأیید کنید. شما به صفحه /home هدایت می شوید و وارد سیستم می شوید.

گفتگویی که از شما می خواهد هویت خود را با رمز عبور یا کلید عبور ذخیره شده خود تأیید کنید.

7. تبریک می گویم!

شما این کد لبه را تمام کردید! اگر سؤالی دارید، از آنها در لیست پستی FIDO-DEV یا در StackOverflow با یک برچسب passkey بپرسید.

بیشتر بدانید