Panduan ini menjelaskan cara menerapkan autentikasi pengguna yang kembali menggunakan kredensial yang dapat ditemukan, baik melalui pemicu tombol eksplisit maupun saran isi otomatis browser yang lancar (UI Bersyarat).
Sisi Server
Pembuatan Opsi
Buat endpoint yang menghasilkan parameter permintaan WebAuthn menggunakan library yang telah diperiksa sesuai standar.
- Gunakan RP ID yang telah ditetapkan: Gunakan RP ID yang tepat dan telah ditetapkan sebagai string konstan.
- Buat verifikasi: Buat buffer acak dengan entropi tinggi dan aman secara kriptografis, simpan dengan aman di sesi pengguna, dan lakukan enkode sebagai Base64URL.
- Pemetaan Discoverable Credentials: Tentukan array kosong
[]untukallowCredentials. Tindakan ini akan meminta kredensial yang dapat ditemukan, yang berarti pengguna tidak perlu memasukkan nama pengguna terlebih dahulu; penyedia kunci sandi akan menampilkan akun yang tersedia. - User Verification level: Tetapkan
userVerification: "preferred"(atau"required"jika diamanatkan secara eksplisit oleh kebijakan kepatuhan perusahaan).- Tingkat batasan
userVerificationyang diminta HARUS dipertahankan di dalam data sesi server di endpoint opsi, bukan diteruskan kembali dari klien melalui string kueri. Hal ini memungkinkan endpoint verifikasi menerapkan batasan pencocokan yang ketat dengan aman tanpa risiko manipulasi klien.
- Tingkat batasan
// Options generation example (discoverable flow)
const options = {
challenge: serverGeneratedBase64UrlChallenge, // High-entropy random challenge stored in session
rpId: "example.com",
allowCredentials: [], // Request discoverable passkeys
userVerification: "preferred",
};
// Persist expected UV level to user session
req.session.expectedUserVerification = "preferred";
Endpoint Verifikasi
Verifikasi pernyataan yang ditampilkan oleh klien dengan aman untuk mengautentikasi pengguna:
- Memvalidasi verifikasi sesi: Terapkan pencocokan verifikasi yang ketat antara respons klien dan verifikasi yang diharapkan yang disimpan dalam sesi.
- Terapkan Preferensi UV:
- Izinkan pengautentikasi tanpa UV (misalnya, kunci layar pengautentikasi dinonaktifkan) jika
expectedUserVerificationsesi meminta"preferred", dengan meneruskanrequireUserVerification: falseke library verifikasi sisi server Anda. Jika diminta"required", terapkan entri biometrik/PIN secara ketat.
- Izinkan pengautentikasi tanpa UV (misalnya, kunci layar pengautentikasi dinonaktifkan) jika
- Error Server Bersih 404: Jika ID kredensial yang ditampilkan oleh klien tidak ditemukan dalam database, tampilkan error HTTP
404eksplisit sehingga klien dapat memicu Signal API.
Logika Sisi Klien
Anotasi Formulir HTML
Beri anotasi pada input nama pengguna dan sandi Anda untuk memanfaatkan UI Bersyarat secara native. Token pelengkapan otomatis menggabungkan parameter spesifikasi webauthn, dan fokus otomatis langsung memicu pop-up isi otomatis browser saat input difokuskan.
<!-- Autocomplete tokens must contain webauthn space-separated -->
<form id="signin-form">
<input
type="text"
name="username"
autocomplete="username webauthn"
autofocus
data-testid="username-field"
/>
<input type="password" name="password" autocomplete="current-password" />
<button type="submit">Sign in</button>
</form>
Alur Tombol Eksplisit
Memicu autentikasi kunci sandi saat pengguna mengklik tombol "Login dengan kunci sandi". Batalkan panggilan isi otomatis formulir (Conditional Get) yang sedang berlangsung sebelum memanggil dialog kunci sandi.
Alur Mediasi Bersyarat (Isi Otomatis Formulir)
Aktifkan saran isi otomatis formulir saat pemuatan halaman untuk menawarkan autentikasi kunci sandi secara native saat pengguna berfokus pada kolom login:
- Deteksi fitur: Panggil
PublicKeyCredential.getClientCapabilities()saat pemuatan halaman dan lewati login dengan kunci sandi jikaconditionalGettidak tersedia. - Opsi dekode: Dekode objek JSON kredensial yang diambil dengan
PublicKeyCredential.parseRequestOptionsFromJSON(). - Memanggil Conditional Get: Panggil
navigator.credentials.get()denganmediation: "conditional"dan teruskan sinyalAbortController. Tindakan ini akan mendaftarkan isi otomatis secara otomatis tanpa merender dialog kunci sandi. - Pemisahan Pengecualian Try/Catch: Gabungkan panggilan
navigator.credentials.getdalam blok try/catch:NotAllowedError: Pengguna membatalkan atau kehabisan waktu saat merespons perintah login kunci sandi.AbortError: Permintaan autentikasi dibatalkan secara terprogram.
- Call Signal API: Gabungkan panggilan
fetch()verifikasi server dalam blok try/catch:- Tampilkan pesan error agar pengguna memahami masalah yang terjadi.
- Panggil
signalUnknownCredential()HANYA saat server secara eksplisit merespons dengan status HTTP404(Kredensial tidak ditemukan) dan pengguna tidak diautentikasi. - Parameter
credentialIdyang diteruskan kesignalUnknownCredential()HARUS berupa string ID kredensial yang dienkode Base64URL (misalnya,encoded.id), BUKAN objek ArrayBuffer mentahcredential.rawId.
- Mengenkode respons: Enkode kredensial
AuthenticatorAssertionResponsedengan.toJSON()sebelum mengirimkannya ke server untuk verifikasi.
// optionsFetch and loginVerifyFetch are app-defined HTTP methods
import { optionsFetch, loginVerifyFetch } from "./api.js";
let autofillAbortController = new AbortController();
async function initializeConditionalAutofill() {
// Feature detect Conditional Get autofill support
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet === true) {
const loginOptionsJSON = await optionsFetch();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);
try {
// Initiate Conditional UI form autofill suggestions
const credential = await navigator.credentials.get({
publicKey,
signal: autofillAbortController.signal,
mediation: "conditional",
});
// Segregated verification fetch
const encoded = credential.toJSON();
const response = await loginVerifyFetch(encoded);
if (!response.ok && response.status === 404) {
// Note: this code path runs pre-authentication, satisfying the unauth precondition
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encoded.id,
});
}
}
} catch (err) {
// Silently swallow expected client WebAuthn exceptions
if (["NotAllowedError", "AbortError"].includes(err.name)) {
return;
}
console.error("Unexpected conditional get error:", err);
}
}
}
async function triggerButtonAuthentication() {
// Abort any pending Conditional Get call to prevent passkey prompt collisions
autofillAbortController.abort();
autofillAbortController = new AbortController(); // Reset controller for next triggers
const loginOptionsJSON = await optionsFetch();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);
let credential;
try {
// Passkey explicit prompt trigger
credential = await navigator.credentials.get({
publicKey,
signal: autofillAbortController.signal,
});
} catch (err) {
if (err.name === "NotAllowedError") {
console.log("User cancelled passkey login.");
} else if (err.name === "AbortError") {
console.log("The authentication operation was aborted.");
}
// Re-arm Conditional autofill Suggestions after cancelled explicit button prompts
initializeConditionalAutofill();
return; // Safe exit
}
// Segregated verification try/catch (HTTP 404 trigger)
const encoded = credential.toJSON();
try {
const response = await loginVerifyFetch(encoded);
if (!response.ok && response.status === 404) {
// Note: this code path runs pre-authentication, satisfying the unauth precondition
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encoded.id, // Base64URL-encoded credential ID
});
}
} catch (serverErr) {
console.error("Verification request error:", serverErr);
}
}
// Trigger Conditional Get on load
window.addEventListener("DOMContentLoaded", initializeConditionalAutofill);
Strategi Penggantian
Penggantian deteksi fitur kunci sandi
Selalu instal 'webauthn-polyfills' dan impor dalam konteks.
Pertimbangkan bahwa selama PublicKeyCredential didukung, PublicKeyCredential.getClientCapabilities juga didukung.
import 'webauthn-polyfills';
Penggantian Sinkronisasi Signal API
WebAuthn Signal API (webauthn-signals) adalah pengoptimalan progresif yang digunakan untuk menjaga pengelola sandi tetap sinkron dengan status kredensial server.
- Pengalaman Pengganti: Dibatasi melalui
if (PublicKeyCredential.signalUnknownCredential). Jika tidak didukung, sinkronisasi verifikasi latar belakang akan dilewati dengan baik tanpa menampilkan pengecualian browser.
Penggantian Serialisasi JSON yang Mudah
Selalu instal 'webauthn-polyfills' dan impor dalam konteks.
Pertimbangkan bahwa selama PublicKeyCredential didukung, PublicKeyCredential.parseRequestOptionsFromJSON dan PublicKeyCredential.prototype.toJSON juga didukung.
import 'webauthn-polyfills';