Mengimplementasikan kunci sandi dengan isi otomatis formulir di aplikasi web

1. Sebelum memulai

Penggunaan kunci sandi sebagai pengganti sandi merupakan cara tepat bagi situs untuk membuat akun penggunanya jadi lebih mudah, lebih simpel, dan lebih aman digunakan. Dengan kunci sandi, pengguna dapat login ke situs atau aplikasi menggunakan fitur kunci layar perangkat, seperti sidik jari, wajah, atau PIN perangkat. Kunci sandi harus dibuat, dikaitkan dengan akun pengguna, dan disimpan kunci publiknya di server sebelum pengguna dapat menggunakannya untuk login.

Dalam codelab ini, Anda akan mengubah proses login yang menggunakan nama pengguna dan sandi berbasis formulir menjadi proses login yang mendukung kunci sandi dan mencakup hal berikut:

  • Tombol yang membuat kunci sandi setelah pengguna login.
  • UI yang menampilkan daftar kunci sandi terdaftar.
  • Formulir login yang sudah ada yang memungkinkan pengguna login dengan kunci sandi terdaftar melalui isi otomatis formulir.

Prasyarat

Yang akan Anda pelajari

  • Cara membuat kunci sandi.
  • Cara mengautentikasi pengguna dengan kunci sandi.
  • Cara membuat formulir menyarankan kunci sandi sebagai opsi login.

Yang Anda butuhkan

Salah satu dari kombinasi perangkat berikut:

  • Google Chrome dengan perangkat Android yang menjalankan Android 9 atau yang lebih tinggi, sebaiknya yang memiliki sensor biometrik.
  • Chrome dengan perangkat Windows yang menjalankan Windows 10 atau yang lebih tinggi.
  • Safari 16 atau yang lebih tinggi dengan iPhone yang menjalankan iOS 16 atau yang lebih tinggi, atau iPad yang menjalankan iPadOS 16 atau yang lebih tinggi.
  • Safari 16 atau yang lebih tinggi, atau Chrome dengan perangkat desktop Apple yang menjalankan macOS Ventura atau yang lebih tinggi.

2. Memulai persiapan

Dalam codelab ini, Anda akan menggunakan layanan bernama Glitch, yang memungkinkan Anda mengedit kode sisi server dan klien dengan JavaScript, lalu men-deploy-nya hanya dari browser.

Membuka project

  1. Buka project di Glitch.
  2. Klik Remix untuk melakukan fork project Glitch.
  3. Pada menu navigasi di bagian bawah Glitch, klik Preview > Preview di jendela baru. Tab baru akan terbuka di browser Anda.

Tombol Preview in a new window pada menu navigasi di bagian bawah Glitch

Memeriksa status awal situs

  1. Di tab pratinjau, masukkan nama pengguna acak, lalu klik Next.
  2. Masukkan sandi acak, lalu klik Sign-in. Sandi akan diabaikan, tetapi Anda masih diautentikasi dan akan diarahkan ke halaman beranda.
  3. Anda dapat mengubah nama tampilan jika perlu. Anda hanya dapat melakukan hal ini dalam status awal.
  4. Klik Sign out.

Dalam status ini, pengguna harus memasukkan sandi setiap kali mereka login. Tambahkan dukungan kunci sandi ke formulir ini agar pengguna dapat login dengan fungsi kunci layar perangkat. Anda dapat mencoba status akhir di https://passkeys-codelab.glitch.me/.

Untuk mengetahui informasi selengkapnya tentang cara kerja kunci sandi, lihat Bagaimana cara kerja kunci sandi?.

3. Menambahkan kemampuan untuk membuat kunci sandi

Agar pengguna dapat melakukan autentikasi dengan kunci sandi, Anda perlu memberi mereka kemampuan untuk membuat dan mendaftarkan kunci sandi, serta menyimpan kunci publiknya di server.

Dialog verifikasi pengguna kunci sandi akan muncul saat pembuatan kunci sandi.

Izinkan pembuatan kunci sandi setelah pengguna login dengan sandi, dan tambahkan UI yang memungkinkan pengguna membuat kunci sandi dan melihat daftar semua kunci sandi yang terdaftar di halaman /home. Di bagian berikutnya, Anda akan membuat fungsi yang membuat dan mendaftarkan kunci sandi.

Membuat fungsi registerCredential()

  1. Di Glitch, buka file public/client.js, lalu scroll ke bagian paling bawah.
  2. Setelah bagian komentar yang relevan, tambahkan fungsi registerCredential() berikut:

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.

};

Fungsi ini membuat dan mendaftarkan kunci sandi di server.

Mendapatkan verifikasi login dan opsi lainnya dari endpoint server

Sebelum kunci sandi dibuat, Anda harus meminta parameter untuk meneruskan WebAuthn dari server, termasuk verifikasi login. WebAuthn adalah API browser yang memungkinkan pengguna membuat kunci sandi dan mengautentikasi pengguna dengan kunci sandi. Untungnya, Anda sudah memiliki endpoint server yang merespons parameter tersebut dalam codelab ini.

  • Untuk mendapatkan verifikasi login dan opsi lainnya dari endpoint server, tambahkan kode berikut ke isi fungsi registerCredential() setelah bagian komentar yang relevan:

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');

Cuplikan kode berikut menyertakan opsi contoh yang Anda terima dari server:

{
  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,
  }
}

Protokol antara server dan klien bukan bagian dari spesifikasi WebAuthn. Namun, server codelab ini dirancang untuk menampilkan JSON yang sangat mirip dengan kamus PublicKeyCredentialCreationOptions yang diteruskan ke API navigator.credentials.create() WebAuthn.

Tabel berikut tidaklah lengkap, tetapi berisi parameter penting dalam kamus PublicKeyCredentialCreationOptions:

Parameter

Deskripsi

challenge

Verifikasi login yang dibuat server di objek ArrayBuffer untuk pendaftaran ini. Wajib diisi, tetapi tidak digunakan selama pendaftaran, kecuali melakukan pengesahan—topik lanjutan yang tidak dibahas dalam codelab ini.

user.id

ID unik pengguna. Nilai ini harus berupa objek ArrayBuffer yang tidak menyertakan informasi identitas pribadi, seperti alamat email atau nama pengguna. Nilai acak 16 byte yang dihasilkan per akun yang berfungsi dengan baik.

user.name

Kolom ini harus berisi ID unik untuk akun yang dapat dikenali pengguna, seperti alamat email atau nama pengguna. ID akun ditampilkan di pemilih akun. (Jika Anda menggunakan nama pengguna, gunakan nilai yang sama dengan autentikasi sandi.)

user.displayName

Kolom ini merupakan nama opsional yang mudah digunakan untuk akun. Kolom ini tidak harus berupa nama yang unik, dan dapat berupa nama pilihan pengguna. Jika situs Anda tidak memiliki nilai yang sesuai untuk disertakan di sini, teruskan string kosong. Informasi ini mungkin ditampilkan di pemilih akun, bergantung pada browser.

rp.id

ID pihak tepercaya (RP) adalah domain. Situs dapat menentukan domain atau akhiran yang dapat didaftarkan. Misalnya, jika asal RP adalah https://login.example.com:1337, ID RP dapat berupa login.example.com atau example.com. Jika ID RP ditetapkan sebagai example.com, pengguna dapat melakukan autentikasi di login.example.com atau subdomain lain dari example.com.

pubKeyCredParams

Kolom ini menentukan algoritma kunci publik yang didukung RP. Sebaiknya tetapkan ke [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]. Tindakan ini menentukan dukungan untuk ECDSA dengan P-256 dan RSA PKCS#1, serta memberikan cakupan lengkap (jika mendukung hal ini).

excludeCredentials

Memberikan daftar ID kredensial yang sudah terdaftar untuk mencegah pendaftaran perangkat yang sama dua kali. Jika disediakan, anggota transports harus berisi hasil pemanggilan fungsi getTransports() selama pendaftaran setiap kredensial.

authenticatorSelection.authenticatorAttachment

Tetapkan ke nilai "platform". Ini menunjukkan bahwa Anda menginginkan pengautentikasi yang disematkan di perangkat platform, sehingga pengguna tidak akan diminta memasukkan kunci keamanan USB atau hal lainnya.

authenticatorSelection.requireResidentKey

Tetapkan ke nilai true Boolean. Kredensial yang dapat ditemukan (kunci yang tersimpan) dapat digunakan tanpa mengharuskan server memberikan ID kredensial, sehingga kompatibel dengan fitur isi otomatis.

authenticatorSelection.userVerification

Tetapkan ke nilai "preferred" atau hapus karena merupakan nilai default. Ini menunjukkan apakah verifikasi pengguna yang menggunakan kunci layar perangkat adalah "required", "preferred", atau "discouraged". Menyetel ke nilai "preferred" akan meminta verifikasi pengguna jika perangkat mendukung.

Membuat kredensial

  1. Dalam isi fungsi registerCredential() setelah komentar yang relevan, konversikan beberapa parameter yang dienkode dengan Base64URL kembali ke biner, khususnya string user.id dan challenge, serta instance string id yang disertakan dalam array 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. Pada baris berikutnya, tetapkan authenticatorSelection.authenticatorAttachment ke "platform" dan authenticatorSelection.requireResidentKey ke true. Hal ini hanya memungkinkan penggunaan pengautentikasi platform (perangkat itu sendiri) dengan kemampuan kredensial yang dapat ditemukan.

public/client.js

// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
  authenticatorAttachment: 'platform',
  requireResidentKey: true
}
  1. Pada baris berikutnya, panggil metode navigator.credentials.create() untuk membuat kredensial.

public/client.js

// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
  publicKey: options,
});

Melalui panggilan ini, browser akan mencoba memverifikasi identitas pengguna dengan kunci layar perangkat.

Mendaftarkan kredensial ke endpoint server

Setelah pengguna memverifikasi identitasnya, kunci sandi akan dibuat dan disimpan. Situs menerima objek kredensial yang berisi kunci publik yang dapat Anda kirim ke server untuk mendaftarkan kunci sandi.

Cuplikan kode berikut berisi objek kredensial contoh:

{
  "id": *****,
  "rawId": *****,
  "type": "public-key",
  "response": {
    "clientDataJSON": *****,
    "attestationObject": *****,
    "transports": ["internal", "hybrid"]
  },
  "authenticatorAttachment": "platform"
}

Tabel berikut tidaklah lengkap, tetapi berisi parameter penting dalam objek PublicKeyCredential:

Parameter

Deskripsi

id

ID yang dienkode Base64URL dari kunci sandi yang dibuat. ID ini membantu browser menentukan apakah kunci sandi yang cocok ada di perangkat saat autentikasi. Nilai ini harus disimpan pada database di backend.

rawId

Versi objek ArrayBuffer dari ID kredensial.

response.clientDataJSON

Data klien yang dienkode objek ArrayBuffer.

response.attestationObject

Objek pengesahan yang dienkode ArrayBuffer. Objek ini berisi informasi penting, seperti ID RP, flag, dan kunci publik.

response.transports

Daftar transpor yang didukung perangkat: "internal" berarti perangkat mendukung kunci sandi. "hybrid" berarti perangkat juga mendukung autentikasi di perangkat lain.

authenticatorAttachment

Menampilkan "platform" saat kredensial ini dibuat di perangkat yang mendukung kunci sandi.

Untuk mengirim objek kredensial ke server, ikuti langkah-langkah berikut:

  1. Lakukan enkode parameter biner kredensial sebagai Base64URL agar dapat dikirim ke server sebagai string:

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. Pada baris berikutnya, kirim objek ke server:

public/client.js

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

Saat Anda menjalankan program, server akan menampilkan HTTP code 200, yang menunjukkan bahwa kredensial tersebut terdaftar.

Sekarang Anda telah memiliki fungsi registerCredential() lengkap.

Meninjau kode solusi untuk bagian ini

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. Membuat UI untuk mendaftarkan dan mengelola kredensial kunci sandi

Setelah fungsi registerCredential() tersedia, Anda memerlukan tombol untuk memanggil fungsi tersebut. Selain itu, Anda perlu menampilkan daftar kunci sandi yang terdaftar.

Kunci sandi yang terdaftar tercantum di /home page

Menambahkan HTML placeholder

  1. Di Glitch, buka file views/home.html.
  2. Setelah komentar yang relevan, tambahkan placeholder UI yang menampilkan tombol untuk mendaftarkan sebuah kunci sandi dan daftar kunci sandi:

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>

Elemen div#list adalah placeholder untuk daftar.

Memeriksa dukungan kunci sandi

Untuk menampilkan opsi untuk membuat kunci sandi hanya kepada pengguna dengan perangkat yang mendukung kunci sandi, Anda harus memeriksa terlebih dahulu apakah WebAuthn tersedia atau tidak. Jika tersedia, Anda harus menghapus class hidden untuk menampilkan tombol Buat kunci sandi.

Untuk memeriksa apakah lingkungan mendukung kunci sandi, ikuti langkah-langkah berikut:

  1. Di akhir file views/home.html setelah komentar yang relevan, tulis kondisional yang akan dijalankan jika window.PublicKeyCredential, PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable, dan PublicKeyCredential.isConditionalMediationAvailable adalah true.

views/home.html

// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Feature detections
if (window.PublicKeyCredential &&
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
    PublicKeyCredential.isConditionalMediationAvailable) {
  1. Di bagian isi kondisional, periksa apakah perangkat dapat membuat kunci sandi, lalu periksa apakah kunci sandi dapat disarankan dalam isi otomatis formulir.

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. Jika semua kondisi terpenuhi, tampilkan tombol untuk membuat kunci sandi. Jika tidak, tampilkan pesan peringatan.

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

Merender kunci sandi terdaftar dalam daftar

  1. Tentukan fungsi renderCredentials() yang mengambil kunci sandi terdaftar dari server dan merendernya dalam daftar. Untungnya, Anda sudah memiliki endpoint server /auth/getKeys untuk mengambil kunci sandi terdaftar bagi pengguna yang login.

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. Pada baris berikutnya, panggil fungsi renderCredentials() untuk menampilkan kunci sandi terdaftar segera setelah pengguna membuka halaman /home sebagai inisialisasi.

views/home.html

renderCredentials();

Membuat dan mendaftarkan kunci sandi

Untuk membuat dan mendaftarkan kunci sandi, Anda harus memanggil fungsi registerCredential() yang telah Anda terapkan sebelumnya.

Untuk memicu fungsi registerCredential() saat mengklik tombol Buat kunci sandi, ikuti langkah-langkah berikut:

  1. Di file setelah HTML placeholder, temukan pernyataan import berikut:

views/home.html

import {
  $,
  _fetch,
  loading,
  updateCredential,
  unregisterCredential,
} from '/client.js';
  1. Di akhir isi pernyataan import, tambahkan fungsi 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. Di akhir file setelah komentar yang relevan, tentukan fungsi register() yang memanggil fungsi registerCredential() dan UI pemuatan, lalu panggil renderCredentials() setelah pendaftaran. Dengan ini, browser akan membuat kunci sandi dan menampilkan pesan error saat terjadi masalah.

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. Dalam isi fungsi register(), tangkap pengecualian. Metode navigator.credentials.create() akan menampilkan error InvalidStateError jika kunci sandi sudah ada di perangkat. Langkah ini diperiksa dengan array excludeCredentials. Dalam kasus ini, Anda menampilkan pesan yang relevan kepada pengguna. Tindakan ini juga menampilkan error NotAllowedError jika pengguna membatalkan dialog autentikasi. Dalam kasus ini, Anda diam-diam mengabaikannya.

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. Pada baris setelah fungsi register(), lampirkan fungsi register() ke peristiwa click untuk tombol Buat kunci sandi.

views/home.html

createPasskey.addEventListener('click', register);

Meninjau kode solusi untuk bagian ini

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

Cobalah

Jika sudah mengikuti semua langkah sejauh ini, coba terapkan kemampuan untuk membuat, mendaftarkan, dan menampilkan kunci sandi di situs.

Untuk mencobanya, ikuti langkah-langkah berikut:

  1. Di tab pratinjau, login dengan nama pengguna dan sandi acak.
  2. Klik Buat kunci sandi.
  3. Verifikasi identitas Anda dengan kunci layar perangkat.
  4. Pastikan kunci sandi terdaftar dan muncul di bagian Kunci sandi terdaftar dari halaman web.

Kunci sandi yang terdaftar tercantum di /home page

Mengganti nama dan menghapus kunci sandi terdaftar

Anda seharusnya dapat mengganti nama atau menghapus kunci sandi terdaftar dalam daftar. Anda dapat memeriksa cara kerjanya dalam kode yang disertakan dengan codelab.

Di Chrome, Anda dapat menghapus kunci sandi terdaftar dari chrome://settings/passkeys di desktop atau dari pengelola sandi di setelan pada Android.

Untuk informasi tentang cara mengganti nama dan menghapus kunci sandi terdaftar di platform lain, lihat masing-masing halaman dukungan untuk platform tersebut.

5. Menambahkan kemampuan untuk melakukan autentikasi dengan kunci sandi

Pengguna kini dapat membuat dan mendaftarkan kunci sandi, serta dapat menggunakannya sebagai cara untuk melakukan autentikasi ke situs dengan aman. Sekarang Anda perlu menambahkan kemampuan autentikasi kunci sandi ke situs.

Membuat fungsi authenticate()

  • Di file public/client.js setelah komentar yang relevan, buat fungsi bernama authenticate() yang memverifikasi pengguna dan server secara lokal:

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.

};

Mendapatkan verifikasi login dan opsi lainnya dari endpoint server

Sebelum meminta pengguna untuk mengautentikasi, Anda harus meminta parameter untuk meneruskan WebAuthn dari server, termasuk verifikasi login.

  • Dalam isi fungsi authenticate() setelah komentar yang relevan, panggil fungsi _fetch() untuk mengirim permintaan POST ke server:

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');

Server codelab ini dirancang untuk menampilkan JSON yang sangat mirip dengan kamus PublicKeyCredentialRequestOptions yang diteruskan ke API navigator.credentials.get() WebAuthn. Cuplikan kode berikut menyertakan opsi contoh yang harus Anda terima:

{
  "challenge": *****,
  "rpId": "passkeys-codelab.glitch.me",
  "allowCredentials": []
}

Tabel berikut tidaklah lengkap, tetapi berisi parameter penting dalam kamus PublicKeyCredentialRequestOptions:

Parameter

Deskripsi

challenge

Verifikasi login yang dibuat server di objek ArrayBuffer. Hal ini diperlukan untuk mencegah serangan replay. Jangan terima verifikasi login yang sama dalam satu respons. Pertimbangkan token CSRF.

rpId

ID RP adalah domain. Situs dapat menentukan domain atau akhiran yang dapat didaftarkan. Nilai ini harus cocok dengan parameter rp.id yang digunakan saat kunci sandi dibuat.

allowCredentials

Properti ini digunakan untuk menemukan pengautentikasi yang memenuhi syarat untuk autentikasi ini. Teruskan array kosong atau biarkan tidak ditentukan agar browser menampilkan pemilih akun.

userVerification

Tetapkan ke nilai "preferred" atau hapus karena merupakan nilai default. Ini menunjukkan apakah verifikasi pengguna yang menggunakan kunci layar perangkat adalah "required", "preferred", atau "discouraged". Menyetel ke nilai "preferred" akan meminta verifikasi pengguna jika perangkat mendukung.

Memverifikasi pengguna secara lokal dan mendapatkan kredensial

  1. Dalam isi fungsi authenticate() setelah komentar yang relevan, konversikan parameter challenge kembali ke biner:

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. Teruskan array kosong ke parameter allowCredentials untuk membuka pemilih akun saat pengguna melakukan autentikasi:

public/client.js

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

Pemilih akun menggunakan informasi pengguna yang disimpan dengan kunci sandi.

  1. Panggil metode navigator.credentials.get() bersama dengan opsi mediation: 'conditional':

public/client.js

// Invoke the WebAuthn get() method.
const cred = await navigator.credentials.get({
  publicKey: options,

  // Request a conditional UI.
  mediation: 'conditional'
});

Opsi ini menginstruksikan browser agar menyarankan kunci sandi secara kondisional sebagai bagian dari isi otomatis formulir.

Memverifikasi kredensial

Setelah pengguna memverifikasi identitasnya secara lokal, Anda akan menerima objek kredensial berisi tanda tangan yang dapat diverifikasi di server.

Cuplikan kode berikut menyertakan contoh objek PublicKeyCredential:

{
  "id": *****,
  "rawId": *****,
  "type": "public-key",
  "response": {
    "clientDataJSON": *****,
    "authenticatorData": *****,
    "signature": *****,
    "userHandle": *****
  },
  authenticatorAttachment: "platform"
}

Tabel berikut tidaklah lengkap, tetapi berisi parameter penting dalam objek PublicKeyCredential:

Parameter

Deskripsi

id

ID yang dienkode Base64URL dari kredensial kunci sandi yang diautentikasi.

rawId

Versi objek ArrayBuffer dari ID kredensial.

response.clientDataJSON

Objek ArrayBuffer data klien. Kolom ini berisi informasi, seperti verifikasi login dan asal yang perlu diverifikasi oleh server RP.

response.authenticatorData

Objek ArrayBuffer data pengautentikasi. Kolom ini berisi informasi seperti ID RP.

response.signature

Objek ArrayBuffer tanda tangan. Nilai ini adalah inti kredensial dan harus diverifikasi di server.

response.userHandle

Objek ArrayBuffer yang berisi ID pengguna yang ditetapkan pada waktu pembuatan. Nilai ini dapat digunakan sebagai ganti ID kredensial jika server perlu memilih nilai ID yang digunakannya, atau jika backend ingin menghindari pembuatan indeks pada ID kredensial.

authenticatorAttachment

Tampilkan string "platform" jika kredensial berasal dari perangkat lokal. Jika tidak, tampilkan string "cross-platform", terutama saat pengguna menggunakan ponsel untuk login. Jika pengguna perlu menggunakan ponsel untuk login, minta mereka untuk membuat kunci sandi di perangkat lokal.

Untuk mengirim objek kredensial ke server, ikuti langkah-langkah berikut:

  1. Dalam isi fungsi authenticate() setelah komentar yang relevan, lakukan enkode parameter biner kredensial agar parameter tersebut dapat dikirim ke server sebagai string:

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. Kirim objek ke server:

public/client.js

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

Saat Anda menjalankan program, server akan menampilkan HTTP code 200, yang menunjukkan bahwa kredensial tersebut telah diverifikasi.

Anda sekarang memiliki fungsi authentication() lengkap.

Meninjau kode solusi untuk bagian ini

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. Menambahkan kunci sandi ke isi otomatis browser

Saat pengguna kembali, buat pengguna login semudah dan seaman mungkin. Jika Anda menambahkan tombol Login dengan kunci sandi ke halaman login, pengguna dapat menekan tombol tersebut, memilih kunci sandi di pemilih akun browser, dan menggunakan kunci layar untuk memverifikasi identitas.

Namun, peralihan dari sandi ke kunci sandi tidak dapat dilakukan untuk semua pengguna secara sekaligus. Artinya, Anda tidak dapat menghapus sandi sampai semua pengguna beralih ke kunci sandi. Anda harus menutup formulir login berbasis sandi sampai proses tersebut selesai. Namun, jika Anda menutup formulir sandi dan tombol kunci sandi, pengguna harus memilih di antara kedua cara tersebut untuk login, yang seharusnya tidak diperlukan. Idealnya, buat proses login yang mudah.

Di sinilah UI kondisional dapat membantu. UI kondisional adalah fitur WebAuthn yang memungkinkan Anda membuat kolom input formulir untuk menyarankan kunci sandi sebagai bagian dari item isi otomatis selain sandi. Jika pengguna mengetuk kunci sandi dalam saran isi otomatis, pengguna akan diminta menggunakan kunci layar perangkat untuk memverifikasi identitasnya secara lokal. Hal ini adalah pengalaman pengguna yang lancar karena tindakan login pengguna hampir sama dengan login berbasis sandi.

Kunci sandi disarankan sebagai bagian dari isi otomatis formulir.

Mengaktifkan UI kondisional

Untuk mengaktifkan UI kondisional, Anda hanya perlu menambahkan token webauthn dalam atribut autocomplete kolom input. Setelah token ditetapkan, Anda dapat memanggil metode navigator.credentials.get() dengan string mediation: 'conditional' untuk memicu UI kunci layar secara kondisional.

  • Untuk mengaktifkan UI kondisional, ganti kolom input nama pengguna yang ada dengan HTML berikut setelah komentar yang relevan dalam file view/index.html:

view/index.html

<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
  type="text"
  id="username"
  class="mdc-text-field__input"
  aria-labelledby="username-label"
  name="username"
  autocomplete="username webauthn"
  autofocus />

Mendeteksi fitur, memanggil WebAuthn, dan mengaktifkan UI kondisional

  1. Di file view/index.html setelah komentar yang relevan, ganti pernyataan import yang ada dengan kode berikut:

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";

Kode ini mengimpor fungsi authenticate() yang Anda telah terapkan sebelumnya.

  1. Pastikan objek window.PulicKeyCredential tersedia dan metode PublicKeyCredential.isConditionalMediationAvailable() menampilkan nilai true, lalu panggil fungsi 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);
    }
  }
}

Meninjau kode solusi untuk bagian ini

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

Cobalah

Coba buat, daftarkan, tampilkan, dan lakukan autentikasi kunci sandi di situs Anda.

Untuk mencobanya, ikuti langkah-langkah berikut:

  1. Buka tab pratinjau.
  2. Jika perlu, logout.
  3. Klik kotak teks nama pengguna. Dialog akan muncul.
  4. Pilih akun yang ingin Anda gunakan untuk login.
  5. Verifikasi identitas Anda dengan kunci layar perangkat. Anda akan dialihkan ke halaman /home dan login.

Sebuah dialog yang meminta Anda untuk memverifikasi identitas dengan sandi atau kunci sandi yang tersimpan.

7. Selamat!

Anda telah menyelesaikan codelab ini. Jika ada pertanyaan, kirimkan ke milis FIDO-DEV atau di StackOverflow dengan tag passkey.

Pelajari lebih lanjut