Mem-build aplikasi WebAuthn pertama Anda

1. Sebelum memulai

Web Authentication API, yang juga dikenal sebagai WebAuthn, memungkinkan Anda membuat dan menggunakan kredensial kunci publik dengan cakupan asal untuk mengautentikasi pengguna.

API tersebut mendukung penggunaan pengautentikasi BLE, NFC, dan U2F atau FIDO2 roaming USB (yang juga disebut kunci keamanan) serta pengautentikasi platform, yang memungkinkan pengguna mengautentikasi dengan sidik jari atau kunci layar mereka.

Dalam codelab ini, Anda akan mem-build situs dengan fungsi autentikasi ulang sederhana yang menggunakan sensor sidik jari. Autentikasi ulang melindungi data akun karena mewajibkan pengguna yang sudah login ke situs untuk melakukan autentikasi ulang saat mereka mencoba memasuki bagian penting situs atau mengunjungi kembali situs setelah jangka waktu tertentu.

Prasyarat

  • Pemahaman dasar tentang cara kerja WebAuthn
  • Keterampilan pemrograman dasar dengan JavaScript

Yang akan Anda lakukan

  • Mem-build situs dengan fungsi autentikasi ulang sederhana yang menggunakan sensor sidik jari

Yang akan Anda perlukan

  • Salah satu perangkat berikut:
    • Perangkat Android, sebaiknya dengan sensor biometrik
    • iPhone atau iPad dengan Touch ID atau Face ID pada iOS 14 atau lebih tinggi
    • MacBook Pro atau Air dengan Touch ID di macOS Big Sur atau yang lebih tinggi
    • Windows 10 19H1 atau yang lebih tinggi dengan penyiapan Windows Hello
  • Salah satu browser berikut:
    • Google Chrome 67 atau yang lebih tinggi
    • Microsoft Edge 85 atau yang lebih tinggi
    • Safari 14 atau yang lebih tinggi

2. Memulai persiapan

Dalam codelab ini, Anda akan menggunakan layanan yang disebut glitch. Di sinilah Anda dapat mengedit kode sisi server dan klien dengan JavaScript, dan langsung men-deploynya.

Buka https://glitch.com/edit/#!/webauthn-codelab-start.

Lihat cara kerjanya

Ikuti langkah-langkah berikut untuk melihat status awal situs:

  1. Klik 62bb7a6aac381af8.png Show > 3343769d04c09851.png In a New Window untuk melihat situs aktif.
  2. Masukkan nama pengguna pilihan Anda, lalu klik Next.
  3. Masukkan sandi dan klik Sign-in.

Sandi diabaikan, tetapi Anda masih terautentikasi. Anda akan diarahkan ke halaman beranda.

  1. Klik Try reauth, dan ulangi langkah kedua, ketiga, dan keempat.
  2. Klik Sign out.

Perhatikan bahwa Anda harus memasukkan sandi setiap kali Anda mencoba login. Tindakan ini mengemulasikan pengguna yang perlu melakukan autentikasi ulang sebelum mereka dapat mengakses bagian penting pada situs.

Melakukan remix kode

  1. Buka Codelab WebAuthn / FIDO2 API.
  2. Klik nama project Anda > Remix Project 306122647ce93305.png untuk melakukan fork project dan melanjutkan dengan versi Anda sendiri di URL baru.

8d42bd24f0fd185c.png

3. Mendaftarkan kredensial dengan sidik jari

Anda harus mendaftarkan kredensial yang dihasilkan oleh UVPA, pengautentikasi yang terintegrasi di dalam perangkat dan memverifikasi identitas pengguna. Ini biasanya dianggap sebagai sensor sidik jari, bergantung pada perangkat pengguna.

Tambahkan fitur ini ke halaman /home:

260aab9f1a2587a7.png

Membuat fungsi registerCredential()

Buat fungsi registerCredential(), yang mendaftarkan kredensial baru.

public/client.js

export const registerCredential = async () => {

};

Mendapatkan verifikasi login dan opsi lainnya dari endpoint server

Sebelum meminta pengguna untuk mendaftarkan kredensial baru, minta server agar menampilkan parameter untuk meneruskan WebAuthn, termasuk verifikasi login. Untungnya, Anda sudah memiliki endpoint server yang merespons dengan parameter tersebut.

Tambahkan kode berikut ke registerCredential().

public/client.js

const opts = {
  attestation: 'none',
  authenticatorSelection: {
    authenticatorAttachment: 'platform',
    userVerification: 'required',
    requireResidentKey: false
  }
};

const options = await _fetch('/auth/registerRequest', opts);

Protokol antara server dan klien bukan bagian dari spesifikasi WebAuthn. Namun, codelab ini dirancang agar sesuai dengan spesifikasi WebAuthn, dan objek JSON yang Anda teruskan ke server sangat mirip dengan PublicKeyCredentialCreationOptions sehingga ini intuitif bagi Anda. Tabel berikut berisi parameter penting yang dapat Anda teruskan ke server dan menjelaskan tindakan yang dilakukan parameter:

Parameter

Deskripsi

attestation

Preferensi untuk pernyataan pengesahan—none, indirect, atau direct. Pilih none kecuali Anda memerlukannya.

excludeCredentials

Array PublicKeyCredentialDescriptor agar pengautentikasi dapat menghindari pembuatan duplikat.

authenticatorSelection

authenticatorAttachment

Filter pengautentikasi yang tersedia. Jika Anda ingin pengautentikasi terhubung ke perangkat, gunakan "platform". Untuk pengautentikasi roaming, gunakan "cross-platform".

userVerification

Tentukan apakah verifikasi pengguna lokal pengautentikasi adalah "required", "preferred", atau "discouraged". Jika Anda menginginkan autentikasi sidik jari atau kunci layar, gunakan "required".

requireResidentKey

Gunakan true jika kredensial yang dibuat harus tersedia untuk UX pemilih akun mendatang.

Untuk mempelajari opsi ini lebih lanjut, lihat 5.4. Opsi untuk Pembuatan Kredensial (kamus PublicKeyCredentialCreationOptions).

Berikut adalah opsi contoh yang Anda terima dari server.

{
  "rp": {
    "name": "WebAuthn Codelab",
    "id": "webauthn-codelab.glitch.me"
  },
  "user": {
    "displayName": "User Name",
    "id": "...",
    "name": "test"
  },
  "challenge": "...",
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }, {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "userVerification": "required"
  }
}

Membuat kredensial

  1. Karena opsi ini dikirimkan dan dienkode untuk melalui protokol HTTP, konversikan beberapa parameter kembali ke biner, khususnya, user.id, challenge, dan instance id yang disertakan dalam array excludeCredentials:

public/client.js

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. Panggil metode navigator.credentials.create() untuk membuat kredensial baru.

Dengan panggilan ini, browser berinteraksi dengan pengautentikasi dan mencoba memverifikasi identitas pengguna dengan UVPA.

public/client.js

const cred = await navigator.credentials.create({
  publicKey: options,
});

Setelah pengguna memverifikasi identitasnya, Anda akan menerima objek kredensial yang dapat dikirim ke server dan mendaftarkan pengautentikasi.

Mendaftarkan kredensial ke endpoint server

Berikut adalah contoh objek kredensial yang seharusnya Anda terima.

{
  "id": "...",
  "rawId": "...",
  "type": "public-key",
  "response": {
    "clientDataJSON": "...",
    "attestationObject": "..."
  }
}
  1. Seperti saat Anda menerima objek opsi untuk mendaftarkan kredensial, enkode parameter biner kredensial sehingga dapat dikirim ke server sebagai string:

public/client.js

const credential = {};
credential.id = cred.id;
credential.rawId = base64url.encode(cred.rawId);
credential.type = cred.type;

if (cred.response) {
  const clientDataJSON =
    base64url.encode(cred.response.clientDataJSON);
  const attestationObject =
    base64url.encode(cred.response.attestationObject);
  credential.response = {
    clientDataJSON,
    attestationObject,
  };
}
  1. Simpan ID kredensial secara lokal sehingga Anda dapat menggunakannya untuk autentikasi saat pengguna kembali:

public/client.js

localStorage.setItem(`credId`, credential.id);
  1. Kirim objek ke server dan, jika objek menampilkan HTTP code 200, pertimbangkan bahwa kredensial baru tersebut berhasil didaftarkan.

public/client.js

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

Anda sekarang memiliki fungsi registerCredential() yang lengkap.

Kode akhir untuk bagian ini

public/client.js

...
export const registerCredential = async () => {
  const opts = {
    attestation: 'none',
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      userVerification: 'required',
      requireResidentKey: false
    }
  };

  const options = await _fetch('/auth/registerRequest', opts);

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

  const cred = await navigator.credentials.create({
    publicKey: options
  });

  const credential = {};
  credential.id =     cred.id;
  credential.rawId =  base64url.encode(cred.rawId);
  credential.type =   cred.type;

  if (cred.response) {
    const clientDataJSON =
      base64url.encode(cred.response.clientDataJSON);
    const attestationObject =
      base64url.encode(cred.response.attestationObject);
    credential.response = {
      clientDataJSON,
      attestationObject
    };
  }

  localStorage.setItem(`credId`, credential.id);

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

4. Mem-build UI untuk mendaftarkan, mendapatkan, dan menghapus kredensial

Akan sangat menyenangkan jika Anda memiliki daftar kredensial terdaftar dan tombol untuk menghapusnya.

9b5b5ae4a7b316bd.png

Mem-build placeholder UI

Tambahkan UI untuk mencantumkan kredensial dan tombol untuk mendaftarkan kredensial baru. Bergantung pada apakah fitur ini tersedia atau tidak, hapus class hidden dari pesan peringatan atau tombol untuk mendaftarkan kredensial baru. ul#list adalah placeholder untuk menambahkan daftar kredensial terdaftar.

views/home.html

<p id="uvpa_unavailable" class="hidden">
  This device does not support User Verifying Platform Authenticator. You can't register a credential.
</p>
<h3 class="mdc-typography mdc-typography--headline6">
  Your registered credentials:
</h3>
<section>
  <div id="list"></div>
</section>
<mwc-button id="register" class="hidden" icon="fingerprint" raised>Add a credential</mwc-button>

Deteksi fitur dan ketersediaan UVPA

Ikuti langkah-langkah berikut untuk memeriksa ketersediaan UVPA:

  1. Cermati window.PublicKeyCredential untuk memeriksa apakah WebAuthn tersedia.
  2. Panggil PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() untuk memeriksa apakah UVPA tersedia. Jika tersedia, tampilkan tombol untuk mendaftarkan kredensial baru. Jika salah satunya tidak tersedia, tampilkan pesan peringatan.

views/home.html

const register = document.querySelector('#register');

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa) {
      register.classList.remove('hidden');
    } else {
      document
        .querySelector('#uvpa_unavailable')
        .classList.remove('hidden');
    }
  });
} else {
  document
    .querySelector('#uvpa_unavailable')
    .classList.remove('hidden');
}

Mendapatkan dan menampilkan daftar kredensial

  1. Buat fungsi getCredentials() agar Anda bisa mendapatkan kredensial terdaftar dan menampilkannya di daftar. Untungnya, Anda sudah memiliki endpoint praktis di /auth/getKeys server tempat Anda dapat mengambil kredensial yang terdaftar untuk pengguna yang login.

JSON yang ditampilkan menyertakan informasi kredensial, seperti id dan publicKey. Anda dapat mem-build HTML untuk menampilkannya kepada pengguna.

views/home.html

const getCredentials = async () => {
  const res = await _fetch('/auth/getKeys');
  const list = document.querySelector('#list');
  const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
    <div class="mdc-card credential">
      <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
      <pre class="public-key">${cred.publicKey}</pre>
      <div class="mdc-card__actions">
        <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
      </div>
    </div>`) : html`
    <p>No credentials found.</p>
    `}`;
  render(creds, list);
};
  1. Panggil getCredentials() untuk menampilkan kredensial yang tersedia segera setelah pengguna membuka halaman /home.

views/home.html

getCredentials();

Menghapus kredensial

Dalam daftar kredensial, Anda menambahkan tombol untuk menghapus setiap kredensial. Anda dapat mengirim permintaan ke /auth/removeKey beserta parameter kueri credId untuk menghapusnya.

public/client.js

export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
  1. Tambahkan unregisterCredential ke pernyataan import yang ada.

views/home.html

import { _fetch, unregisterCredential } from '/client.js';
  1. Tambahkan fungsi yang akan dipanggil saat pengguna mengklik Remove.

views/home.html

const removeCredential = async e => {
  try {
    await unregisterCredential(e.target.id);
    getCredentials();
  } catch (e) {
    alert(e);
  }
};

Mendaftarkan kredensial

Anda dapat memanggil registerCredential() untuk mendaftarkan kredensial baru saat pengguna mengklik Add a credential.

  1. Tambahkan registerCredential ke pernyataan import yang ada.

views/home.html

import { _fetch, registerCredential, unregisterCredential } from '/client.js';
  1. Panggil registerCredential() dengan opsi untuk navigator.credentials.create().

Jangan lupa untuk memperbarui daftar kredensial dengan memanggil getCredentials() setelah pendaftaran.

views/home.html

register.addEventListener('click', e => {
  registerCredential().then(user => {
    getCredentials();
  }).catch(e => alert(e));
});

Sekarang Anda dapat mendaftarkan kredensial baru dan menampilkan informasi tentang kredensial tersebut. Anda dapat mencobanya di situs aktif.

Kode akhir untuk bagian ini

views/home.html

...
      <p id="uvpa_unavailable" class="hidden">
        This device does not support User Verifying Platform Authenticator. You can't register a credential.
      </p>
      <h3 class="mdc-typography mdc-typography--headline6">
        Your registered credentials:
      </h3>
      <section>
        <div id="list"></div>
        <mwc-fab id="register" class="hidden" icon="add"></mwc-fab>
      </section>
      <mwc-button raised><a href="/reauth">Try reauth</a></mwc-button>
      <mwc-button><a href="/auth/signout">Sign out</a></mwc-button>
    </main>
    <script type="module">
      import { _fetch, registerCredential, unregisterCredential } from '/client.js';
      import { html, render } from 'https://unpkg.com/lit-html@1.0.0/lit-html.js?module';

      const register = document.querySelector('#register');

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa) {
            register.classList.remove('hidden');
          } else {
            document
              .querySelector('#uvpa_unavailable')
              .classList.remove('hidden');
          }
        });
      } else {
        document
          .querySelector('#uvpa_unavailable')
          .classList.remove('hidden');
      }

      const getCredentials = async () => {
        const res = await _fetch('/auth/getKeys');
        const list = document.querySelector('#list');
        const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
          <div class="mdc-card credential">
            <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
            <pre class="public-key">${cred.publicKey}</pre>
            <div class="mdc-card__actions">
              <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
            </div>
          </div>`) : html`
          <p>No credentials found.</p>
          `}`;
        render(creds, list);
      };

      getCredentials();

      const removeCredential = async e => {
        try {
          await unregisterCredential(e.target.id);
          getCredentials();
        } catch (e) {
          alert(e);
        }
      };

      register.addEventListener('click', e => {
        registerCredential({
          attestation: 'none',
          authenticatorSelection: {
            authenticatorAttachment: 'platform',
            userVerification: 'required',
            requireResidentKey: false
          }
        })
        .then(user => {
          getCredentials();
        })
        .catch(e => alert(e));
      });
    </script>
...

public/client.js

...
export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
...

5. Mengautentikasi pengguna dengan sidik jari

Anda sekarang memiliki kredensial terdaftar dan siap digunakan sebagai cara untuk mengautentikasi pengguna. Sekarang, tambahkan fungsi autentikasi ulang ke situs. Berikut adalah pengalaman pengguna:

Saat berada di halaman /reauth, pengguna akan melihat tombol Authenticate jika autentikasi biometrik memungkinkan. Autentikasi dengan sidik jari (UVPA) dimulai saat pengguna mengetuk Authenticate, berhasil mengautentikasi, lalu membuka halaman /home. Jika autentikasi biometrik tidak tersedia atau autentikasi dengan biometrik gagal, UI akan kembali untuk menggunakan formulir sandi yang ada.

b8770c4e7475b075.png

Membuat fungsi authenticate()

Buat fungsi yang disebut authenticate(), yang memverifikasi identitas pengguna dengan sidik jari. Tambahkan kode JavaScript di sini:

public/client.js

export const authenticate = async () => {

};

Mendapatkan verifikasi login dan opsi lainnya dari endpoint server

  1. Sebelum autentikasi, periksa apakah pengguna memiliki ID kredensial tersimpan dan tetapkan sebagai parameter kueri jika pengguna memilikinya.

Jika Anda memberikan ID kredensial beserta opsi lainnya, server dapat memberikan allowCredentials yang relevan dan ini akan membuat verifikasi pengguna menjadi andal.

public/client.js

const opts = {};

let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
  url += `?credId=${encodeURIComponent(credId)}`;
}
  1. Sebelum meminta pengguna untuk mengautentikasi, minta server untuk mengirimkan kembali verifikasi dan parameter lainnya. Panggil _fetch() dengan opts sebagai argumen untuk mengirim permintaan POST ke server.

public/client.js

const options = await _fetch(url, opts);

Berikut adalah opsi contoh yang harus Anda terima (sesuai dengan PublicKeyCredentialRequestOptions).

{
  "challenge": "...",
  "timeout": 1800000,
  "rpId": "webauthn-codelab.glitch.me",
  "userVerification": "required",
  "allowCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ]
}

Opsi yang paling penting di sini adalah allowCredentials. Saat Anda menerima opsi dari server, allowCredentials harus berupa objek tunggal dalam array atau array kosong, bergantung pada apakah kredensial dengan ID dalam parameter kueri ditemukan di sisi server.

  1. Resolve promise dengan null jika allowCredentials adalah array kosong sehingga UI kembali untuk meminta sandi.
if (options.allowCredentials.length === 0) {
  console.info('No registered credentials found.');
  return Promise.resolve(null);
}

Memverifikasi pengguna secara lokal dan mendapatkan kredensial

  1. Karena opsi ini dikirimkan dan dienkode untuk melalui protokol HTTP, konversikan beberapa parameter kembali ke biner, khususnya challenge dan instance idyang disertakan dalam array allowCredentials:

public/client.js

options.challenge = base64url.decode(options.challenge);

for (let cred of options.allowCredentials) {
  cred.id = base64url.decode(cred.id);
}
  1. Panggil metode navigator.credentials.get() untuk memverifikasi identitas pengguna dengan UVPA.

public/client.js

const cred = await navigator.credentials.get({
  publicKey: options
});

Setelah pengguna memverifikasi identitasnya, Anda akan menerima objek kredensial yang dapat dikirim ke server dan mengautentikasikan pengguna.

Memverifikasi kredensial

Berikut adalah contoh objek PublicKeyCredential (response adalah AuthenticatorAssertionResponse) yang akan Anda terima:

{
  "id": "...",
  "type": "public-key",
  "rawId": "...",
  "response": {
    "clientDataJSON": "...",
    "authenticatorData": "...",
    "signature": "...",
    "userHandle": ""
  }
}
  1. Enkodekan parameter biner kredensial agar dapat dikirim ke server sebagai string:

public/client.js

const credential = {};
credential.id = cred.id;
credential.type = cred.type;
credential.rawId = base64url.encode(cred.rawId);

if (cred.response) {
  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 dan, jika menampilkan HTTP code 200, anggaplah pengguna berhasil login:

public/client.js

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

Anda sekarang memiliki fungsi authentication() yang lengkap.

Kode akhir untuk bagian ini

public/client.js

...
export const authenticate = async () => {
  const opts = {};

  let url = '/auth/signinRequest';
  const credId = localStorage.getItem(`credId`);
  if (credId) {
    url += `?credId=${encodeURIComponent(credId)}`;
  }

  const options = await _fetch(url, opts);

  if (options.allowCredentials.length === 0) {
    console.info('No registered credentials found.');
    return Promise.resolve(null);
  }

  options.challenge = base64url.decode(options.challenge);

  for (let cred of options.allowCredentials) {
    cred.id = base64url.decode(cred.id);
  }

  const cred = await navigator.credentials.get({
    publicKey: options
  });

  const credential = {};
  credential.id = cred.id;
  credential.type = cred.type;
  credential.rawId = base64url.encode(cred.rawId);

  if (cred.response) {
    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. Mengaktifkan pengalaman autentikasi ulang

Mem-build UI

Saat pengguna kembali, Anda ingin mereka mengautentikasi ulang semudah dan seaman mungkin. Di sinilah autentikasi biometrik berfungsi. Namun, ada kasus saat autentikasi biometrik mungkin tidak berfungsi:

  • UVPA tidak tersedia.
  • Pengguna belum mendaftarkan kredensial apa pun di perangkatnya.
  • Penyimpanan dihapus dan perangkat tidak lagi mengingat ID kredensial.
  • Pengguna tidak dapat memverifikasi identitasnya karena beberapa alasan, seperti saat jari mereka basah atau mereka mengenakan masker.

Itulah sebabnya Anda harus selalu memberikan opsi login lain sebagai cadangan. Dalam codelab ini, gunakan solusi sandi berbasis formulir.

19da999b0145054.png

  1. Tambahkan UI untuk menampilkan tombol autentikasi yang memanggil autentikasi biometrik selain formulir sandi.

Gunakan class hidden untuk menampilkan dan menyembunyikan salah satunya secara selektif, bergantung pada status pengguna.

views/reauth.html

<div id="uvpa_available" class="hidden">
  <h2>
    Verify your identity
  </h2>
  <div>
    <mwc-button id="reauth" raised>Authenticate</mwc-button>
  </div>
  <div>
    <mwc-button id="cancel">Sign-in with password</mwc-button>
  </div>
</div>
  1. Tambahkan class="hidden" ke formulir:

views/reauth.html

<form id="form" method="POST" action="/auth/password" class="hidden">

Deteksi fitur dan ketersediaan UVPA

Pengguna harus login menggunakan sandi jika salah satu kondisi berikut terpenuhi:

  • WebAuthn tidak tersedia.
  • UVPA tidak tersedia.
  • ID kredensial untuk UVPA ini tidak dapat ditemukan.

Tampilkan tombol autentikasi secara selektif atau sembunyikan:

views/reauth.html

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa && localStorage.getItem(`credId`)) {
      document
        .querySelector('#uvpa_available')
        .classList.remove('hidden');
    } else {
      form.classList.remove('hidden');
    }
  });
} else {
  form.classList.remove('hidden');
}

Kembali ke formulir sandi

Pengguna juga dapat memilih untuk login dengan sandi.

Tampilkan formulir sandi dan sembunyikan tombol autentikasi saat pengguna mengklik Sign in with password:.

views/reauth.html

const cancel = document.querySelector('#cancel');
cancel.addEventListener('click', e => {
  form.classList.remove('hidden');
  document
    .querySelector('#uvpa_available')
    .classList.add('hidden');
});

c4a82800889f078c.png

Memanggil autentikasi biometrik

Terakhir, aktifkan autentikasi biometrik.

  1. Tambahkan authenticate ke pernyataan import yang ada:

views/reauth.html

import { _fetch, authenticate } from '/client.js';
  1. Panggil authenticate() saat pengguna mengetuk Authenticate untuk memulai autentikasi biometrik.

Pastikan bahwa kegagalan pada autentikasi biometrik akan mengembalikan pengguna ke formulir sandi.

views/reauth.html

const button = document.querySelector('#reauth');
button.addEventListener('click', e => {
  authenticate().then(user => {
    if (user) {
      location.href = '/home';
    } else {
      throw 'User not found.';
    }
  }).catch(e => {
    console.error(e.message || e);
    alert('Authentication failed. Use password to sign-in.');
    form.classList.remove('hidden');
    document.querySelector('#uvpa_available').classList.add('hidden');
  });
});

Kode akhir untuk bagian ini

views/reauth.html

...
    <main class="content">
      <div id="uvpa_available" class="hidden">
        <h2>
          Verify your identity
        </h2>
        <div>
          <mwc-button id="reauth" raised>Authenticate</mwc-button>
        </div>
        <div>
          <mwc-button id="cancel">Sign-in with password</mwc-button>
        </div>
      </div>
      <form id="form" method="POST" action="/auth/password" class="hidden">
        <h2>
          Enter a password
        </h2>
        <input type="hidden" name="username" value="{{username}}" />
        <div class="mdc-text-field mdc-text-field--filled">
          <span class="mdc-text-field__ripple"></span>
          <label class="mdc-floating-label" id="password-label">password</label>
          <input type="password" class="mdc-text-field__input" aria-labelledby="password-label" name="password" />
          <span class="mdc-line-ripple"></span>
        </div>
        <input type="submit" class="mdc-button mdc-button--raised" value="Sign-In" />
        <p class="instructions">password will be ignored in this demo.</p>
      </form>
    </main>
    <script src="https://unpkg.com/material-components-web@7.0.0/dist/material-components-web.min.js"></script>
    <script type="module">
      new mdc.textField.MDCTextField(document.querySelector('.mdc-text-field'));
      import { _fetch, authenticate } from '/client.js';
      const form = document.querySelector('#form');
      form.addEventListener('submit', e => {
        e.preventDefault();
        const form = new FormData(e.target);
        const cred = {};
        form.forEach((v, k) => cred[k] = v);
        _fetch(e.target.action, cred)
        .then(user => {
          location.href = '/home';
        })
        .catch(e => alert(e));
      });

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa && localStorage.getItem(`credId`)) {
            document
              .querySelector('#uvpa_available')
              .classList.remove('hidden');
          } else {
            form.classList.remove('hidden');
          }
        });
      } else {
        form.classList.remove('hidden');
      }

      const cancel = document.querySelector('#cancel');
      cancel.addEventListener('click', e => {
        form.classList.remove('hidden');
        document
          .querySelector('#uvpa_available')
          .classList.add('hidden');
      });

      const button = document.querySelector('#reauth');
      button.addEventListener('click', e => {
        authenticate().then(user => {
          if (user) {
            location.href = '/home';
          } else {
            throw 'User not found.';
          }
        }).catch(e => {
          console.error(e.message || e);
          alert('Authentication failed. Use password to sign-in.');
          form.classList.remove('hidden');
          document.querySelector('#uvpa_available').classList.add('hidden');
        });
      });
    </script>
...

7. Selamat!

Anda telah menyelesaikan codelab ini.

Pelajari lebih lanjut

Terima kasih banyak kepada Yuriy Ackermann dari FIDO Alliance atas bantuan Anda.