Buat kunci sandi untuk login tanpa sandi

Kunci sandi membuat akun pengguna lebih aman, lebih sederhana, dan lebih mudah digunakan.

Dipublikasikan: 12 Oktober 2022, Terakhir diperbarui: 9 April 2026

Penggunaan kunci sandi meningkatkan keamanan, menyederhanakan proses login, dan menggantikan sandi. Tidak seperti sandi biasa, yang harus diingat dan dimasukkan secara manual oleh pengguna, kunci sandi menggunakan mekanisme kunci layar perangkat seperti biometrik atau PIN dan mengurangi risiko phishing serta pencurian kredensial.

Kunci sandi disinkronkan di seluruh perangkat menggunakan penyedia kunci sandi seperti Pengelola Sandi Google dan Rantai Kunci iCloud.

Kunci sandi harus dibuat, menyimpan kunci pribadi dengan aman ke penyedia kunci sandi bersama dengan metadata yang diperlukan dan kunci publiknya disimpan di server Anda untuk autentikasi. Kunci pribadi mengeluarkan tanda tangan setelah verifikasi pengguna di domain yang valid sehingga kunci sandi tahan terhadap phishing. Kunci publik memverifikasi tanda tangan tanpa menyimpan kredensial sensitif, sehingga kunci sandi tahan terhadap pencurian kredensial.

Cara kerja pembuatan kunci sandi

Sebelum pengguna dapat login dengan kunci sandi, Anda harus membuat kunci sandi, mengaitkannya dengan akun pengguna, dan menyimpan kunci publiknya di server Anda.

Anda dapat meminta pengguna membuat kunci sandi dalam salah satu situasi berikut:

  • Selama atau setelah mendaftar.
  • Setelah login.
  • Setelah login menggunakan kunci sandi dari perangkat lain (yaitu, authenticatorAttachment adalah cross-platform).
  • Di halaman khusus tempat pengguna dapat mengelola kunci sandi mereka.

Untuk membuat kunci sandi, Anda menggunakan WebAuthn API.

Empat komponen alur pendaftaran kunci sandi adalah:

  • Backend: Menyimpan detail akun pengguna, termasuk kunci publik.
  • Frontend: Berkomunikasi dengan browser dan mengambil data yang diperlukan dari backend.
  • Browser: Menjalankan JavaScript Anda dan berinteraksi dengan WebAuthn API.
  • Penyedia kunci sandi: Membuat dan menyimpan kunci sandi. Biasanya berupa pengelola sandi seperti Pengelola Sandi Google, atau kunci keamanan.
Proses pembuatan dan pendaftaran kunci sandi
Proses pembuatan dan pendaftaran kunci sandi.

Sebelum membuat kunci sandi, pastikan sistem memenuhi prasyarat berikut:

  • Akun pengguna diverifikasi melalui metode yang aman (misalnya, email, verifikasi telepon, atau federasi identitas) dalam jangka waktu yang singkat.

  • Frontend dan backend dapat berkomunikasi dengan aman untuk bertukar data kredensial.

  • Browser mendukung WebAuthn dan pembuatan kunci sandi.

Kami dapat menunjukkan cara memeriksa sebagian besar pelanggaran tersebut di bagian berikut.

Setelah sistem memenuhi kondisi ini, proses berikut akan terjadi untuk membuat kunci sandi:

  1. Sistem memicu proses pembuatan kunci sandi saat pengguna memulai tindakan (misalnya, mengklik tombol "Buat Kunci Sandi" di halaman pengelolaan kunci sandi atau setelah menyelesaikan pendaftaran).
  2. Frontend meminta data kredensial yang diperlukan dari backend, termasuk informasi pengguna, tantangan, dan ID kredensial untuk mencegah duplikat.
  3. Frontend memanggil navigator.credentials.create() untuk meminta penyedia kunci sandi perangkat membuat kunci sandi menggunakan informasi dari backend. Perhatikan bahwa panggilan ini menampilkan promise.
  4. Perangkat pengguna mengautentikasi pengguna menggunakan metode biometrik, PIN, atau pola untuk membuat kunci sandi.
  5. Penyedia kunci sandi membuat kunci sandi dan menampilkan kredensial kunci publik ke frontend, sehingga menyelesaikan promise.
  6. Frontend mengirim kredensial kunci publik yang dibuat ke backend.
  7. Backend menyimpan kunci publik dan data penting lainnya untuk autentikasi di masa mendatang,
  8. Backend memberi tahu pengguna (misalnya, menggunakan email) untuk mengonfirmasi pembuatan kunci sandi dan mendeteksi potensi akses tidak sah.

Proses ini memastikan proses pendaftaran kunci sandi yang aman dan lancar bagi pengguna.

Kompatibilitas

Sebagian besar browser mendukung WebAuthn, dengan beberapa kekurangan kecil. Lihat passkeys.dev untuk mengetahui detail kompatibilitas browser dan OS.

Membuat kunci sandi baru

Untuk membuat kunci sandi baru, berikut proses yang harus diikuti frontend:

  1. Periksa kompatibilitas.
  2. Ambil informasi dari backend.
  3. Panggil WebAuth API untuk membuat kunci sandi.
  4. Kirim kunci publik yang ditampilkan ke backend.
  5. Simpan kredensial.

Bagian berikut menunjukkan cara melakukannya.

Memeriksa kompatibilitas

Sebelum menampilkan tombol "Buat kunci sandi baru", frontend harus memeriksa apakah:

  • Browser mendukung WebAuthn dengan PublicKeyCredential.

Browser Support

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Source

  • Browser mendukung deteksi kemampuan dengan PublicKeyCredential.getClientCapabilities().

Browser Support

  • Chrome: 133.
  • Edge: 133.
  • Firefox: 135.
  • Safari: 17.4.

Source

  • Browser mendukung UI bersyarat WebAuthn dengan conditionalGet.

  • Perangkat mendukung pengautentikasi platform (dapat membuat kunci sandi dan mengautentikasi di perangkat) dengan passkeyPlatformAuthenticator.

Cuplikan kode berikut menunjukkan cara memeriksa kompatibilitas sebelum menampilkan opsi terkait kunci sandi.

if (window.PublicKeyCredential && PublicKeyCredential.getClientCapabilities) {
  const capabilities = await PublicKeyCredential.getClientCapabilities();
  if (capabilities.conditionalGet === true &&
      capabilities.passkeyPlatformAuthenticator === true) {
    // The browser supports passkeys and the conditional UI.
  }
}

Dalam contoh ini, tombol Buat kunci sandi baru hanya akan ditampilkan jika semua kondisi terpenuhi.

Mengambil informasi dari backend

Saat pengguna mengklik tombol, ambil informasi yang diperlukan dari backend untuk memanggil navigator.credentials.create().

Cuplikan kode berikut menunjukkan objek JSON dengan informasi yang diperlukan untuk memanggil navigator.credentials.create():

// Example `PublicKeyCredentialCreationOptions` contents
{
  challenge: *****,
  rp: {
    name: "Example",
    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'],
  }],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    requireResidentKey: true,
  }
}

Pasangan nilai kunci dalam objek menyimpan informasi berikut:

  • challenge: Verifikasi login yang dibuat server di ArrayBuffer untuk pendaftaran ini.
  • rp.id: ID RP (ID Pihak Tepercaya), domain, dan situs dapat menentukan domainnya 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 di subdomain mana pun di example.com. Lihat Mengizinkan penggunaan ulang kunci sandi di seluruh situs Anda dengan Permintaan Asal Terkait untuk mengetahui informasi selengkapnya tentang hal ini.
  • rp.name: Nama RP (Pihak Tepercaya). Hal ini tidak digunakan lagi di WebAuthn L3, tetapi disertakan karena alasan kompatibilitas.
  • user.id: ID pengguna unik dalam ArrayBuffer, yang dibuat saat pembuatan akun. ID pengguna harus permanen, tidak seperti nama pengguna yang dapat diedit. ID pengguna mengidentifikasi akun, tetapi tidak boleh berisi informasi identitas pribadi (PII). Anda mungkin sudah memiliki ID pengguna di sistem Anda, tetapi jika diperlukan, buat ID pengguna khusus untuk kunci sandi agar tidak berisi PII.
  • user.name: ID unik untuk akun yang akan dikenali pengguna, seperti alamat email atau nama penggunanya. ID unik ini akan ditampilkan di pemilih akun.
  • user.displayName: Nama yang lebih mudah digunakan dan wajib diisi 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.
  • pubKeyCredParams: Menentukan algoritma kunci publik yang didukung RP (pihak yang mengandalkan). 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: Daftar ID kredensial yang sudah terdaftar. Mencegah pendaftaran perangkat yang sama dua kali dengan memberikan daftar ID kredensial yang sudah terdaftar. Anggota transports, jika tersedia, harus berisi hasil pemanggilan getTransports() selama pendaftaran setiap kredensial.
  • authenticatorSelection.authenticatorAttachment: Tetapkan ini ke "platform" bersama dengan hint: ['client-device'] jika pembuatan kunci sandi ini adalah upgrade dari sandi, misalnya dalam promosi setelah login. "platform" menunjukkan bahwa RP menginginkan pengautentikasi platform (pengautentikasi yang disematkan ke perangkat platform) yang tidak meminta, misalnya, untuk memasukkan kunci keamanan USB. Pengguna memiliki opsi yang lebih sederhana untuk membuat kunci sandi.
  • authenticatorSelection.requireResidentKey: Tetapkan ke boolean true. Kredensial yang dapat ditemukan (kunci resident) menyimpan informasi pengguna ke kunci sandi dan memungkinkan pengguna memilih akun saat autentikasi.
  • authenticatorSelection.userVerification: Menunjukkan apakah verifikasi pengguna yang menggunakan kunci layar perangkat adalah "required", "preferred", atau "discouraged". Defaultnya adalah "preferred", yang berarti pengautentikasi dapat melewati verifikasi pengguna. Tetapkan ini ke "preferred" atau hapus properti.

Sebaiknya buat objek di server, lakukan encoding ArrayBuffer dengan Base64URL, dan ambil dari frontend. Dengan cara ini, Anda dapat mendekode payload menggunakan PublicKeyCredential.parseCreationOptionsFromJSON() dan meneruskannya langsung ke navigator.credentials.create().

Cuplikan kode berikut menunjukkan cara Anda dapat mengambil dan mendekode informasi yang diperlukan untuk membuat kunci sandi.

// Fetch an encoded `PubicKeyCredentialCreationOptions` from the server.
const _options = await fetch('/webauthn/registerRequest');

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

Panggil WebAuthn API untuk membuat kunci sandi

Panggil navigator.credentials.create() untuk membuat kunci sandi baru. API menampilkan janji, menunggu interaksi pengguna yang menampilkan dialog modal.

Browser Support

  • Chrome: 60.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Source

// Invoke WebAuthn to create a passkey.
const credential = await navigator.credentials.create({
  publicKey: options
});

Kirim kredensial kunci publik yang ditampilkan ke backend

Setelah pengguna diverifikasi menggunakan kunci layar perangkat, kunci sandi akan dibuat dan promise diselesaikan dengan menampilkan objek PublicKeyCredential ke frontend.

Janji dapat ditolak karena berbagai alasan. Anda dapat menangani error ini dengan memeriksa properti name objek Error:

  • InvalidStateError: Kunci sandi sudah ada di perangkat. Tidak ada dialog error yang akan ditampilkan kepada pengguna. Situs tidak boleh memperlakukan hal ini sebagai error. Pengguna ingin mendaftarkan perangkat lokal dan perangkat tersebut sudah terdaftar.
  • NotAllowedError: Pengguna telah membatalkan operasi.
  • AbortError: Operasi telah dibatalkan.
  • Pengecualian lainnya: Terjadi error yang tidak terduga. Browser menampilkan dialog error kepada pengguna.

Objek kredensial kunci publik berisi properti berikut:

  • 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 ArrayBuffer dari ID kredensial.
  • response.clientDataJSON: Data klien yang dienkode ArrayBuffer.
  • response.attestationObject: Objek pengesahan yang dienkode ArrayBuffer. Objek ini berisi informasi penting seperti ID RP, flag, dan kunci publik.
  • authenticatorAttachment: Menampilkan "platform" saat kredensial ini dibuat di perangkat yang mendukung kunci sandi.
  • type: Kolom ini selalu ditetapkan ke "public-key".

Enkode objek dengan metode .toJSON(), serialisasikan dengan JSON.stringify(), lalu kirim ke server.

...

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

// Encode and send the credential to the server for verification.  
const response = await fetch('/webauthn/registerResponse', {
  method: 'post',
  credentials: 'same-origin',
  body: result
});
...

Simpan kredensial

Setelah menerima kredensial kunci publik di backend, sebaiknya gunakan library atau solusi sisi server daripada menulis kode Anda sendiri untuk memproses kredensial kunci publik.

Kemudian, Anda dapat menyimpan informasi yang diambil dari kredensial ke database untuk digunakan pada masa mendatang.

Daftar berikut mencakup properti yang direkomendasikan untuk disimpan:

  • ID Kredensial: ID kredensial yang ditampilkan dengan kredensial kunci publik.
  • Nama kredensial: Nama kredensial. Beri nama sesuai penyedia kunci sandi yang membuatnya, yang dapat diidentifikasi berdasarkan AAGUID.
  • ID Pengguna: ID pengguna yang digunakan untuk membuat kunci sandi.
  • Kunci publik: Kunci publik yang ditampilkan dengan kredensial kunci publik. Hal ini diperlukan untuk memverifikasi pernyataan kunci sandi.
  • Tanggal dan waktu pembuatan: Mencatat tanggal dan waktu pembuatan kunci sandi. Hal ini berguna untuk mengidentifikasi kunci sandi.
  • Tanggal dan waktu terakhir digunakan: Mencatat tanggal dan waktu terakhir saat pengguna menggunakan kunci sandi untuk login. Hal ini berguna untuk menentukan kunci sandi mana yang telah digunakan (atau tidak digunakan) oleh pengguna.
  • AAGUID: ID unik penyedia kunci sandi.
  • Flag Kelayakan Pencadangan: benar jika perangkat memenuhi syarat untuk sinkronisasi kunci sandi. Informasi ini membantu pengguna mengidentifikasi kunci sandi yang dapat disinkronkan dan kunci sandi terikat perangkat (tidak dapat disinkronkan) di halaman pengelolaan kunci sandi.

Ikuti petunjuk yang lebih mendetail di Pendaftaran kunci sandi sisi server

Memberi sinyal jika pendaftaran gagal

Jika pendaftaran kunci sandi gagal, hal ini dapat menyebabkan kebingungan bagi pengguna. Jika ada kunci sandi di penyedia kunci sandi dan tersedia untuk pengguna, tetapi kunci publik terkait tidak disimpan di sisi server, upaya login menggunakan kunci sandi tidak akan pernah berhasil dan sulit dipecahkan. Pastikan untuk memberi tahu pengguna jika hal itu terjadi.

Untuk mencegah kondisi tersebut, Anda dapat memberi sinyal kunci sandi yang tidak diketahui kepada penyedia kunci sandi menggunakan Signal API. Dengan memanggil PublicKeyCredential.signalUnknownCredential() dengan ID RP dan ID kredensial, RP dapat memberi tahu penyedia kunci sandi bahwa kredensial yang ditentukan telah dihapus atau tidak ada. Penyedia kunci sandi yang akan menentukan cara menangani sinyal ini, tetapi jika didukung, kunci sandi terkait diharapkan akan dihapus.

// Detect authentication failure due to lack of the credential
if (response.status === 404) {
  // Feature detection
  if (PublicKeyCredential.signalUnknownCredential) {
    await PublicKeyCredential.signalUnknownCredential({
      rpId: "example.com",
      credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA" // base64url encoded credential ID
    });
  } else {
    // Encourage the user to delete the passkey from the password manager nevertheless.
    ...
  }
}

Untuk mempelajari Signal API lebih lanjut, baca artikel Memastikan kunci sandi konsisten dengan kredensial di server Anda dengan Signal API.

Mengirim notifikasi kepada pengguna

Mengirim notifikasi (seperti email) saat kunci sandi didaftarkan akan membantu pengguna mendeteksi akses akun yang tidak sah. Jika penyerang membuat kunci sandi tanpa sepengetahuan pengguna, kunci sandi tersebut akan tetap tersedia untuk penyalahgunaan pada masa mendatang, bahkan setelah sandi diubah. Notifikasi ini akan memberi tahu pengguna dan membantu mencegah hal ini.

Checklist

  • Verifikasi pengguna (sebaiknya menggunakan email atau metode yang aman) sebelum mengizinkannya membuat kunci sandi.
  • Mencegah pembuatan kunci sandi duplikat untuk penyedia kunci sandi yang sama menggunakan excludeCredentials.
  • Simpan AAGUID untuk mengidentifikasi penyedia kunci sandi dan memberi nama kredensial untuk pengguna.
  • Memberi sinyal jika upaya mendaftarkan kunci sandi gagal dengan PublicKeyCredential.signalUnknownCredential().
  • Kirim notifikasi kepada pengguna setelah membuat dan mendaftarkan kunci sandi untuk akunnya.

Resource

Langkah berikutnya: Login dengan kunci sandi melalui isi otomatis formulir.