Hướng dẫn này trình bày chi tiết cách triển khai quy trình xác thực người dùng cũ bằng thông tin xác thực có thể phát hiện, thông qua cả trình kích hoạt nút rõ ràng và đề xuất tự động điền liền mạch của trình duyệt (Giao diện người dùng có điều kiện).
Phía máy chủ
Tạo lựa chọn
Tạo một điểm cuối tạo ra các tham số yêu cầu WebAuthn bằng cách sử dụng thư viện đã được kiểm tra theo tiêu chuẩn.
- Sử dụng mã nhận dạng bên tin cậy được xác định trước: Sử dụng mã nhận dạng bên tin cậy thích hợp được xác định trước làm chuỗi hằng số.
- Tạo thử thách: Tạo một vùng đệm ngẫu nhiên có độ hỗn loạn cao, bảo mật bằng mật mã, lưu trữ an toàn trong phiên của người dùng và mã hoá dưới dạng Base64URL.
- Ánh xạ thông tin xác thực có thể phát hiện: Chỉ định một mảng trống
[]choallowCredentials. Thao tác này yêu cầu thông tin xác thực có thể phát hiện, nghĩa là người dùng không cần nhập tên người dùng trước; nhà cung cấp khoá truy cập sẽ hiển thị các tài khoản có sẵn. - Cấp độ xác minh người dùng: Đặt
userVerification: "preferred"(hoặc"required"nếu chính sách tuân thủ của công ty yêu cầu rõ ràng).- Cấp độ ràng buộc
userVerificationđược yêu cầu PHẢI được duy trì bên trong bản ghi phiên máy chủ tại điểm cuối tuỳ chọn, thay vì được trả về từ ứng dụng khách thông qua chuỗi truy vấn. Điều này cho phép điểm cuối xác minh thực thi các quy tắc ràng buộc nghiêm ngặt về việc so khớp một cách an toàn mà không có nguy cơ bị thao túng ứng dụng.
- Cấp độ ràng buộc
// 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";
Điểm cuối xác minh
Xác minh một cách an toàn câu nhận định do ứng dụng trả về để xác thực người dùng:
- Xác thực thử thách phiên: Thực thi việc so khớp thử thách nghiêm ngặt giữa phản hồi của ứng dụng và thử thách dự kiến được lưu trữ trong phiên.
- Thực thi lựa chọn ưu tiên về UV:
- Cho phép trình xác thực không có UV (ví dụ: trình xác thực đã tắt phương thức khoá màn hình) nếu
expectedUserVerificationcủa phiên yêu cầu"preferred", bằng cách truyềnrequireUserVerification: falseđến thư viện xác minh phía máy chủ. Nếu được yêu cầu"required", hãy thực thi nghiêm ngặt việc nhập dữ liệu sinh trắc học/mã PIN.
- Cho phép trình xác thực không có UV (ví dụ: trình xác thực đã tắt phương thức khoá màn hình) nếu
- Lỗi máy chủ sạch 404: Nếu không tìm thấy mã thông tin xác thực do ứng dụng trả về trong cơ sở dữ liệu, hãy trả về lỗi HTTP
404rõ ràng để ứng dụng có thể kích hoạt Signal API.
Logic phía máy khách
Chú thích biểu mẫu HTML
Chú thích dữ liệu đầu vào tên người dùng và mật khẩu để tận dụng giao diện người dùng có điều kiện theo cách gốc. Mã thông báo tự động hoàn thành kết hợp các tham số thông số kỹ thuật webauthn và tính năng tự động lấy tiêu điểm sẽ kích hoạt cửa sổ bật lên tự động điền của trình duyệt ngay khi dữ liệu đầu vào được lấy tiêu điểm.
<!-- 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>
Quy trình nút rõ ràng
Kích hoạt quy trình xác thực bằng khoá truy cập khi người dùng nhấp vào nút "Đăng nhập bằng khoá truy cập". Huỷ mọi lệnh gọi tự động điền biểu mẫu đang diễn ra (Conditional Get) trước khi gọi lời nhắc khoá truy cập.
Quy trình dàn xếp có điều kiện (Tự động điền biểu mẫu)
Kích hoạt đề xuất tự động điền biểu mẫu khi tải trang để cung cấp tính năng xác thực bằng khoá truy cập ngay khi người dùng nhấp vào các trường đăng nhập:
- Phát hiện tính năng: Gọi
PublicKeyCredential.getClientCapabilities()khi tải trang và bỏ qua bước đăng nhập bằng khoá truy cập nếu không cóconditionalGet. - Tuỳ chọn giải mã: Giải mã đối tượng JSON thông tin xác thực đã tìm nạp bằng
PublicKeyCredential.parseRequestOptionsFromJSON(). - Gọi hàm Get có điều kiện: Gọi
navigator.credentials.get()bằngmediation: "conditional"và truyền tín hiệuAbortController. Thao tác này sẽ đăng ký tính năng tự động điền một cách thầm lặng mà không hiển thị hộp thoại khoá truy cập. - Phân tách ngoại lệ Try/Catch: Gói lệnh gọi
navigator.credentials.gettrong khối try/catch:NotAllowedError: Người dùng đã huỷ hoặc hết thời gian chờ lời nhắc đăng nhập bằng khoá truy cập.AbortError: Yêu cầu xác thực đã bị huỷ theo chương trình.
- Gọi Signal API: Gói lệnh gọi xác minh máy chủ
fetch()trong một khối try/catch:- Hiện thông báo lỗi để người dùng hiểu được vấn đề đã xảy ra.
- CHỈ gọi
signalUnknownCredential()khi máy chủ phản hồi rõ ràng bằng trạng thái HTTP404(Không tìm thấy thông tin xác thực) và người dùng chưa được xác thực. - Tham số
credentialIdđược truyền đếnsignalUnknownCredential()PHẢI là chuỗi mã thông tin xác thực được mã hoá Base64URL (ví dụ:encoded.id), KHÔNG PHẢI là đối tượng ArrayBuffer thôcredential.rawId.
- Mã hoá phản hồi: Mã hoá thông tin xác thực
AuthenticatorAssertionResponsebằng.toJSON()trước khi gửi thông tin đó đến máy chủ để xác minh.
// 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);
Chiến lược dự phòng
Dự phòng phát hiện tính năng khoá truy cập
Luôn cài đặt "webauthn-polyfills" và nhập vào ngữ cảnh.
Hãy cân nhắc rằng miễn là PublicKeyCredential được hỗ trợ, thì PublicKeyCredential.getClientCapabilities cũng được hỗ trợ.
import 'webauthn-polyfills';
Dự phòng đồng bộ hoá API tín hiệu
WebAuthn Signal API (webauthn-signals) là một tính năng tối ưu hoá tăng dần dùng để đồng bộ hoá trình quản lý mật khẩu với trạng thái thông tin xác thực của máy chủ.
- Trải nghiệm dự phòng: Được kiểm soát thông qua
if (PublicKeyCredential.signalUnknownCredential). Nếu không được hỗ trợ, quá trình đồng bộ hoá xác minh trong nền sẽ được bỏ qua một cách linh hoạt mà không gửi ngoại lệ của trình duyệt.
Dự phòng chuyển đổi tuần tự JSON dễ dàng
Luôn cài đặt "webauthn-polyfills" và nhập vào ngữ cảnh.
Hãy xem xét miễn là PublicKeyCredential được hỗ trợ, PublicKeyCredential.parseRequestOptionsFromJSON và PublicKeyCredential.prototype.toJSON cũng được hỗ trợ.
import 'webauthn-polyfills';