Buat kunci sandi untuk login tanpa sandi

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

Menggunakan kunci sandi sebagai pengganti sandi adalah cara yang tepat bagi situs untuk membuat akun penggunanya jadi lebih aman, lebih simpel, lebih mudah digunakan, dan tanpa sandi. Dengan kunci sandi, pengguna dapat login ke situs atau aplikasi hanya dengan menggunakan sidik jari, wajah, atau PIN perangkat mereka.

Kunci sandi harus dibuat, dikaitkan dengan akun pengguna, dan disimpan kunci publiknya di server Anda sebelum pengguna dapat menggunakannya untuk login.

Cara kerjanya

Pengguna dapat diminta untuk membuat kunci sandi dalam salah satu situasi berikut:

  • Saat pengguna login menggunakan sandi.
  • Saat pengguna 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: Server backend Anda yang menyimpan database akun yang menyimpan kunci publik dan metadata lainnya tentang kunci sandi.
  • Frontend: Frontend Anda yang berkomunikasi dengan browser dan mengirim permintaan pengambilan ke backend.
  • Browser: Browser pengguna yang menjalankan JavaScript Anda.
  • Authenticator: Pengautentikasi pengguna yang membuat dan menyimpan kunci sandi. Hal ini dapat mencakup pengelola sandi di perangkat yang sama dengan browser (misalnya, saat menggunakan Windows Hello) atau di perangkat lain, seperti ponsel.
Diagram pendaftaran kunci sandi

Proses untuk menambahkan kunci sandi baru ke akun pengguna yang ada adalah sebagai berikut:

  1. Pengguna login ke situs.
  2. Setelah login, pengguna akan meminta untuk membuat kunci sandi di frontend, misalnya, dengan menekan tombol "Buat kunci sandi".
  3. Frontend meminta informasi dari backend untuk membuat kunci sandi, seperti informasi pengguna, tantangan, dan ID kredensial yang akan dikecualikan.
  4. Frontend memanggil navigator.credentials.create() untuk membuat kunci sandi. Panggilan ini menampilkan promise.
  5. Kunci sandi dibuat setelah pengguna mengizinkan penggunaan kunci layar perangkat. Promise diselesaikan dan kredensial kunci publik ditampilkan ke frontend.
  6. Frontend mengirim kredensial kunci publik ke backend dan menyimpan ID kredensial dan kunci publik yang terkait dengan akun pengguna untuk autentikasi mendatang.

Kompatibilitas

WebAuthn didukung oleh sebagian besar browser, tetapi ada sedikit kesenjangan. Lihat Dukungan Perangkat - passkeys.dev untuk mempelajari kombinasi browser dan sistem operasi yang mendukung pembuatan kunci sandi.

Membuat kunci sandi baru

Berikut cara frontend beroperasi setelah permintaan untuk membuat kunci sandi baru.

Deteksi fitur

Sebelum menampilkan tombol "Buat kunci sandi baru", periksa apakah:

  • Browser mendukung WebAuthn dengan PublicKeyCredential.

Dukungan Browser

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

Sumber

  • Perangkat mendukung pengautentikasi platform (dapat membuat kunci sandi dan melakukan autentikasi dengan kunci sandi) dengan PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().

Dukungan Browser

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

Sumber

Dukungan Browser

  • Chrome: 108.
  • Edge: 108.
  • Firefox: 119.
  • Safari: 16.

Sumber

// Availability of `window.PublicKeyCredential` means WebAuthn is usable.  
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.  
// `​​isConditionalMediationAvailable` means the feature detection is usable.  
if (window.PublicKeyCredential &&  
   
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&  
   
PublicKeyCredential.​​isConditionalMediationAvailable) {  
 
// Check if user verifying platform authenticator is available.  
 
Promise.all([  
   
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),  
   
PublicKeyCredential.​​isConditionalMediationAvailable(),  
 
]).then(results => {  
   
if (results.every(r => r === true)) {  
     
// Display "Create a new passkey" button  
   
}  
 
});  
}  

Hingga semua kondisi terpenuhi, kunci sandi tidak akan didukung di browser ini. Tombol "Buat kunci sandi baru" tidak akan ditampilkan hingga saat itu.

Mengambil informasi penting dari backend

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

  • challenge: Verifikasi login yang dibuat server di ArrayBuffer untuk pendaftaran ini. Kolom ini diperlukan, tetapi tidak digunakan selama pendaftaran, kecuali jika melakukan pengesahan—topik lanjutan yang tidak dibahas di sini.
  • user.id: ID unik pengguna. Nilai ini harus berupa ArrayBuffer yang tidak menyertakan informasi identitas pribadi, misalnya alamat email atau nama pengguna. Sebaiknya gunakan nilai acak 16 byte yang dihasilkan per akun.
  • user.name: Kolom ini harus berisi ID unik untuk akun yang akan dikenali pengguna, seperti alamat email atau nama pengguna. ID ini akan ditampilkan di pemilih akun. (Jika menggunakan nama pengguna, gunakan nilai yang sama seperti pada autentikasi sandi.)
  • user.displayName: Kolom ini adalah nama wajib yang lebih mudah digunakan untuk akun. Nama ini tidak harus 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.
  • excludeCredentials: Mencegah pendaftaran perangkat yang sama dengan memberikan daftar ID kredensial yang sudah terdaftar. Anggota transports, jika disediakan, harus berisi hasil pemanggilan getTransports() selama pendaftaran setiap kredensial.

Memanggil WebAuthn API untuk membuat kunci sandi

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

Dukungan Browser

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

Sumber

const publicKeyCredentialCreationOptions = {
  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,
 
}
};

const credential = await navigator.credentials.create({
  publicKey
: publicKeyCredentialCreationOptions
});

// Encode and send the credential to the server for verification.  

Parameter yang tidak dijelaskan di atas adalah:

  • rp.id: ID RP adalah domain dan 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 di subdomain apa pun di example.com.

  • rp.name: Nama RP.

  • 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).

  • authenticatorSelection.authenticatorAttachment: Tetapkan ke "platform" jika pembuatan kunci sandi ini merupakan upgrade dari sandi, misalnya dalam promosi setelah login. "platform" menunjukkan bahwa RP menginginkan pengautentikasi platform (pengautentikasi yang disematkan ke perangkat platform) yang tidak akan meminta untuk memasukkan, misalnya kunci keamanan USB. Pengguna memiliki opsi yang lebih sederhana untuk membuat kunci sandi.

  • authenticatorSelection.requireResidentKey: Tetapkan ke boolean "true". Kredensial yang dapat ditemukan (kunci tetap) menyimpan informasi pengguna ke kunci sandi dan memungkinkan pengguna memilih akun setelah autentikasi. Pelajari kredensial yang dapat ditemukan lebih lanjut di Pendalaman kredensial yang dapat ditemukan

  • authenticatorSelection.userVerification: Menunjukkan apakah verifikasi pengguna yang menggunakan kunci layar perangkat adalah "required", "preferred", atau "discouraged". Setelan defaultnya adalah "preferred", yang berarti pengautentikasi dapat melewati verifikasi pengguna. Tetapkan ini ke "preferred" atau hapus properti.

Mengirim kredensial kunci publik yang ditampilkan ke backend

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

Promise 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 dan situs tidak boleh memperlakukannya sebagai error—pengguna ingin perangkat lokal didaftarkan dan sudah terdaftar.
  • NotAllowedError: Pengguna telah membatalkan operasi.
  • 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 di database di backend.
  • rawId: Versi ArrayBuffer 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".

Jika Anda menggunakan library untuk menangani objek kredensial kunci publik di backend, sebaiknya kirim seluruh objek ke backend setelah mengenkodenya sebagian dengan base64url.

Menyimpan kredensial

Setelah menerima kredensial kunci publik di backend, teruskan ke library FIDO untuk memproses objek.

Kemudian, Anda dapat menyimpan informasi yang diambil dari kredensial ke database untuk digunakan di masa mendatang. Daftar berikut mencakup beberapa properti umum yang akan disimpan:

  • ID Kredensial (Kunci utama)
  • ID Pengguna
  • Kunci publik

Kredensial kunci publik juga menyertakan informasi berikut yang mungkin ingin Anda simpan dalam database:

Ikuti petunjuk yang lebih mendetail di Pendaftaran kunci sandi sisi server

Untuk mengautentikasi pengguna, baca Login dengan kunci sandi melalui isi otomatis formulir.

Resource