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

۱. قبل از شروع

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

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

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

پیش‌نیازها

آنچه یاد خواهید گرفت

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

۲. آماده شوید

در این آزمایشگاه کد، شما یک برنامه آزمایشی ناقص را از گیت‌هاب کپی می‌کنید و سپس پیاده‌سازی پشتیبانی از کلید عبور را به پایان می‌رسانید.

پروژه را کلون کنید

  1. پروژه را در گیت‌هاب باز کنید.
  2. پروژه را کپی یا دانلود کنید.

ac587c53b746785a.png

اجرای پروژه

  1. یک ترمینال باز کنید و cd start دایرکتوری را تغییر دهید.
  2. برای نصب وابستگی‌های پروژه npm install اجرا کنید.
  3. پروژه را با npm run build && IS_LOCAL=1 npm run start ساخته و اجرا کنید.
  4. آدرس http://localhost:8080/ را در مرورگر خود باز کنید.

بررسی وضعیت اولیه وب‌سایت

  1. در سایت، یک نام کاربری تصادفی وارد کنید و سپس روی «بعدی» کلیک کنید.
  2. یک رمز عبور تصادفی وارد کنید و سپس روی ورود کلیک کنید. رمز عبور نادیده گرفته می‌شود، اما شما همچنان احراز هویت شده و به صفحه اصلی هدایت می‌شوید.
  3. اگر می‌خواهید نام نمایشی خود را تغییر دهید، این کار را انجام دهید. این تمام کاری است که می‌توانید در حالت اولیه انجام دهید.
  4. روی خروج از سیستم کلیک کنید.

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

برای اطلاعات بیشتر در مورد نحوه عملکرد کلیدهای عبور، به بخش «کلیدهای عبور چگونه کار می‌کنند؟» مراجعه کنید.

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

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

9b84dbaec66afe9c.png

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

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

  1. در ویرایشگر کد مورد نظر خود، پوشه start ) را باز کنید.
  2. به فایل public/client.js بروید و سپس تا انتها اسکرول کنید.
  3. بعد از کامنت مربوطه، تابع 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 یک API مرورگر است که به کاربر اجازه می‌دهد یک کلید عبور ایجاد کند و کاربر را با کلید عبور احراز هویت کند. خوشبختانه، شما از قبل یک نقطه پایانی سرور دارید که با چنین پارامترهایی در این codelab پاسخ می‌دهد.

  • برای دریافت چالش و سایر گزینه‌ها از نقطه پایانی سرور، کد زیر را به بدنه تابع 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 نیست. با این حال، سرور این codelab به گونه‌ای طراحی شده است که JSON را برگرداند که تا حد امکان شبیه به دیکشنری PublicKeyCredentialCreationOptions باشد که به API مربوط به WebAuthn navigator.credentials.create() ارسال می‌شود.

جدول زیر جامع نیست، اما شامل پارامترهای مهم در دیکشنری PublicKeyCredentialCreationOptions است:

پارامترها

توضیحات

challenge

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

user.id

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

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 قرار دارند را به حالت دودویی (باینری) برگردانید. این کار را می‌توان با تابع PublicKeyCredential.parseCreationOptionsFromJSON() انجام داد:

فایل public/client.js

// TODO: Add an ability to create a passkey: Create a credential.

// Deserialize and decode the `PublicKeyCredential.parseCreationOptionsFromJSON()`.
const options = PublicKeyCredential.parseCreationOptionsFromJSON(_options);
  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 کدگذاری کنید تا بتوان آن را به صورت رشته به سرور تحویل داد. می‌توانید .toJSON() برای انجام این کار استفاده کنید:

فایل public/client.js

// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.

// Encode and serialize the `PublicKeyCredential`.
const credential = JSON.stringify(cred);
  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 the server endpoint.

  const _options = await _fetch('/auth/registerRequest');

  // TODO: Add an ability to create a passkey: Create a credential.

  // Deserialize and decode the `PublicKeyCredential.parseCreationOptionsFromJSON()`.
  const options = PublicKeyCredential.parseCreationOptionsFromJSON(_options);

  // 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.

  // Encode and serialize the `PublicKeyCredential`.
  const credential = JSON.stringify(cred);

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

۴. یک رابط کاربری برای ثبت و مدیریت اعتبارنامه‌های رمز عبور بسازید

حالا که تابع registerCredential() در دسترس است، به یک دکمه برای فراخوانی آن نیاز دارید. همچنین، باید لیستی از رمزهای عبور ثبت شده را نمایش دهید.

bfa4e7cdda47669e.png

اضافه کردن حفره یا سوراخ HTML

  1. در ویرایشگر خود، به فایل views/home.html بروید.
  2. بعد از کامنت مربوطه، یک جای‌نگهدار رابط کاربری اضافه کنید که دکمه‌ای برای ثبت رمز عبور و لیستی از رمزهای عبور را نمایش دهد:

نمایش‌ها/خانه.html

​​<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
  <h3>Your registered passkeys:</h3>
  <div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mdui-button id="create-passkey" class="hidden" icon="fingerprint" type="button">Create a passkey</mdui-button>

عنصر div#list محل قرارگیری لیست را مشخص می‌کند.

پشتیبانی از رمز عبور را بررسی کنید

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

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

  1. در انتهای فایل views/home.html و پس از کامنت مربوطه، یک شرط بنویسید که در صورت true window.PublicKeyCredential ، PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable و PublicKeyCredential.isConditionalMediationAvailable اجرا شود.

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

نمایش‌ها/خانه.html

try {
    const capabilities = await PublicKeyCredential.getClientCapabilities();
    // Is conditional UI available in this browser?
    if (capabilities.conditionalGet === true &&
        capabilities.passkeyPlatformAuthenticator === true) {
  1. اگر همه شرایط برقرار باشد، دکمه ایجاد رمز عبور نمایش داده می‌شود. در غیر این صورت، یک پیام هشدار نمایش داده می‌شود.

نمایش‌ها/خانه.html

      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 را برای دریافت کلیدهای عبور ثبت‌شده برای کاربر وارد شده دارید.

نمایش‌ها/خانه.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 = res.length > 0 ? html`
    <mdui-list>
      ${res.map(cred => html`
        <mdui-list-item>
          ${cred.name || 'Unnamed'}
          <mdui-button-icon data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed'}" @click="${rename}" icon="edit" slot="end-icon"></mdui-button-icon>
          <mdui-button-icon data-cred-id="${cred.id}" @click="${remove}" icon="delete" slot="end-icon"></mdui-button-icon>
        </mdui-list-item>`)}
    </mdui-list>` : html`
    <mdui-list>
      <mdui-list-item>No credentials found.</mdui-list-item>
    </mdui-list>`;
  render(creds, list);
};
  1. در خط بعدی، تابع renderCredentials() را فراخوانی کنید تا به محض ورود کاربر به صفحه /home کلیدهای عبور ثبت شده را به عنوان مقداردهی اولیه نمایش دهد.

نمایش‌ها/خانه.html

renderCredentials();

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

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

برای اجرای تابع registerCredential() هنگام کلیک بر روی دکمه‌ی Create a passkey ، مراحل زیر را دنبال کنید:

  1. در فایل بعد از HTML جایگذاری شده، عبارت import زیر را پیدا کنید:

نمایش‌ها/خانه.html

import { 
  $, 
  _fetch, 
  loading, 
  updateCredential, 
  unregisterCredential, 
} from '/client.js';
  1. در انتهای بدنه‌ی دستور import ، تابع registerCredential() را اضافه کنید.

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

نمایش‌ها/خانه.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 را نشان می‌دهد. در این حالت، شما آن را بی‌سروصدا نادیده می‌گیرید.

نمایش‌ها/خانه.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 متصل کنید.

نمایش‌ها/خانه.html

createPasskey.addEventListener('click', register);

کد راه‌حل این بخش را بررسی کنید

نمایش‌ها/خانه.html

<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
  <h3>Your registered passkeys:</h3>
  <div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mdui-button id="create-passkey" icon="fingerprint" type="button">Create a passkey</mdui-button>

نمایش‌ها/خانه.html

// TODO: Add an ability to create a passkey: Create and register a passkey.
import { 
  $, 
  _fetch, 
  loading, 
  updateCredential, 
  unregisterCredential, 
  registerCredential 
} from '/client.js';

نمایش‌ها/خانه.html

// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');

// Is WebAuthn available in this browser?
if (window.PublicKeyCredential &&
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
  PublicKeyCredential.isConditionalMediationAvailable) {
  try {
    const capabilities = await PublicKeyCredential.getClientCapabilities();
    // Is conditional UI available in this browser?
    if (capabilities.conditionalGet === true &&
      capabilities.passkeyPlatformAuthenticator === 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`
    <mdui-list>
      ${res.map(cred => html`
        <mdui-list-item>
          ${cred.name || 'Unnamed'}
          <mdui-button-icon data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed'}" @click="${rename}" icon="edit" slot="end-icon"></mdui-button-icon>
          <mdui-button-icon data-cred-id="${cred.id}" @click="${remove}" icon="delete" slot="end-icon"></mdui-button-icon>
        </mdui-list-item>`)}
    </mdui-list>` : html`
    <mdui-list>
      <mdui-list-item>No credentials found.</mdui-list-item>
    </mdui-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 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://settings/passkeys در دسکتاپ یا از password manager در تنظیمات در اندروید حذف کنید.

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

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

اکنون کاربران می‌توانند یک کلید عبور ایجاد و ثبت کنند و آماده استفاده از آن به عنوان راهی برای تأیید اعتبار در وب‌سایت شما به صورت ایمن هستند. اکنون باید قابلیت تأیید اعتبار با کلید عبور را به وب‌سایت خود اضافه کنید.

تابع 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.

// Base64URL decode the challenge.
const options = PublicKeyCredential.parseRequestOptionsFromJSON(_options);

سرور این codelab به گونه‌ای طراحی شده است که JSON را تا حد امکان مشابه دیکشنری PublicKeyCredentialRequestOptions که به WebAuthn navigator.credentials.get() API ارسال می‌شود، برگرداند. قطعه کد زیر شامل گزینه‌های نمونه‌ای است که باید دریافت کنید:

{
  "challenge": *****,
  "rpId": "localhost",
  "allowCredentials": []
}

جدول زیر جامع نیست، اما شامل پارامترهای مهم در دیکشنری PublicKeyCredentialRequestOptions است:

پارامترها

توضیحات

challenge

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

rpId

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

allowCredentials

این ویژگی برای یافتن احراز هویت‌کنندگان واجد شرایط برای این احراز هویت استفاده می‌شود. برای اینکه مرورگر بتواند انتخابگر حساب را نمایش دهد، یک آرایه خالی به آن ارسال کنید یا آن را نامشخص بگذارید. درباره نحوه رفتار 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 است.

response.signature

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

response.userHandle

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

authenticatorAttachment

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

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

  1. در بدنه تابع authenticate() پس از توضیحات مربوطه، پارامترهای دودویی اعتبارنامه را کدگذاری کنید تا بتوان آن را به صورت رشته به سرور تحویل داد. می‌توانید .toJSON() برای انجام این کار استفاده کنید:

فایل public/client.js

// TODO: Add an ability to authenticate with a passkey: Verify the credential.
// Encode and serialize the `PublicKeyCredential`.
const credential = JSON.stringify(cred);
  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);
};

۶. اضافه کردن رمز عبور به قابلیت تکمیل خودکار فرم‌ها در مرورگر

وقتی کاربر برمی‌گردد، شما می‌خواهید که کاربر تا حد امکان به راحتی و با امنیت کامل وارد سیستم شود. اگر دکمه‌ی « ورود با کلید عبور» را به صفحه‌ی ورود اضافه کنید، کاربر می‌تواند دکمه را فشار دهد، یک کلید عبور را در انتخابگر حساب مرورگر انتخاب کند و از قفل صفحه برای تأیید هویت استفاده کند.

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

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

d616744939063451.png

فعال کردن رابط کاربری شرطی

برای فعال کردن یک رابط کاربری شرطی، تنها کاری که باید انجام دهید این است که یک توکن webauthn را در ویژگی autocomplete یک فیلد ورودی اضافه کنید. با تنظیم توکن، می‌توانید متد navigator.credentials.get() را با رشته mediation: 'conditional' فراخوانی کنید تا رابط کاربری قفل صفحه به صورت شرطی فعال شود.

  • برای فعال کردن یک رابط کاربری شرطی، فیلدهای ورودی نام کاربری موجود را با کد HTML زیر پس از کامنت مربوطه در فایل view/index.html جایگزین کنید:

نمایش/فهرست.html

<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<mdui-text-field id="username" label="Username" name="username" autocomplete="username webauthn" autofocus></mdui-text-field>

تشخیص ویژگی‌ها، فراخوانی WebAuthn و فعال کردن یک رابط کاربری مشروط

  1. در فایل view/index.html پس از کامنت مربوطه، عبارت import موجود را با کد زیر جایگزین کنید:

نمایش/فهرست.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() را فراخوانی کنید:

نمایش/فهرست.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
if (window.PublicKeyCredential &&
    PublicKeyCredential.getClientCapabilities) {
  try {

    // Is conditional UI available in this browser?
      const capabilities = await PublicKeyCredential.getClientCapabilities();
      if (capabilities.conditionalGet) {

      // 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);
    }
  }
}

کد راه‌حل این بخش را بررسی کنید

نمایش/فهرست.html

<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<mdui-text-field id="username" label="Username" name="username" autocomplete="username webauthn" autofocus></mdui-text-field>

نمایش/فهرست.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import { 
  $, 
  _fetch, 
  loading, 
  authenticate 
} from '/client.js';

نمایش/فهرست.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.        

// Is WebAuthn available on this browser?
if (window.PublicKeyCredential &&
    PublicKeyCredential.getClientCapabilities) {
  try {
    // Is conditional UI available in this browser?
    const capabilities = await PublicKeyCredential.getClientCapabilities();
    if (capabilities.conditionalGet) {
      // 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);
    }
  }
}

امتحانش کن

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

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

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

پنجره‌ای که از شما می‌خواهد هویت خود را با رمز عبور یا کلید عبور ذخیره شده خود تأیید کنید.

۷. تبریک می‌گویم!

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

بیشتر بدانید