Hướng dẫn này trình bày chi tiết cách cho phép người dùng đăng ký khoá truy cập cho tài khoản của họ, cung cấp một giải pháp thay thế đăng nhập không cần mật khẩu có độ bảo mật cao, chống lừa đảo.
Yêu cầu về cơ sở dữ liệu
Để hỗ trợ việc đăng ký khoá truy cập, bảng thông tin xác thực trong cơ sở dữ liệu của bạn phải lưu trữ các trường sau:
export interface StoredPasskeyCredential {
id: string; // Base64URL-encoded credential ID (unique lookup key)
passkeyUserId: string; // Associated application user ID
credentialPublicKey: string; // Base64URL-encoded public key used to verify assertion signatures
credentialType: "public-key";
credentialDeviceType: "singleDevice" | "multiDevice"; // Helps distinguish device-bound vs cloud-synced passkeys
credentialBackedUp: boolean; // Boolean backup state reported by the authenticator
aaguid: string; // Authenticator Attestation GUID
providerIcon?: string; // Provider icon derived from the AAGUID registry (dark or light theme URLs)
name: string; // Provider name derived from AAGUID registry
transports: string[]; // Array of transport names (e.g. 'internal', 'hybrid') necessary for exclusion options
lastUsedAt?: number; // Optional epoch timestamp of last sign-in
registeredAt: number; // Registration epoch timestamp
counter: number; // Authenticator sign-in signature counter used to prevent replay attacks
}
Phía máy chủ
Tạo lựa chọn
Tạo một điểm cuối để tạo các tham số tạo WebAuthn. Sử dụng thư viện đã được kiểm tra theo tiêu chuẩn của danh mục thay vì tự triển khai mật mã.
- 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 một thử thách an toàn: 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ã trên máy chủ, lưu trữ an toàn trong phiên của người dùng và mã hoá dưới dạng Base64URL để phân phối các tuỳ chọn.
- Tránh khoá truy cập trùng lặp: Liên kết mã thông tin xác thực đã đăng ký trước của người dùng với mảng tuỳ chọn
excludeCredentials. Điều này ngăn trình xác thực đăng ký thông tin xác thực trùng lặp trên cùng một tài khoản nhà cung cấp khoá truy cập. - Thực thi thông tin xác thực có thể phát hiện: Đặt
requireResidentKey: truevàresidentKey: "required"trong các tuỳ chọnauthenticatorSelectionđể yêu cầu thông tin xác thực có thể phát hiện, điều này là cần thiết cho việc đăng nhập có thể phát hiện. - Định cấu hình tính năng Xác minh người dùng: Chỉ định
userVerification: "preferred"hoặcuserVerification: "required". Nhiều trường hợp sử dụng liên quan đến việc tuân thủ (ví dụ: tài chính, chăm sóc sức khoẻ) yêu cầu'required'phải thực thi quy trình xác minh người dùng khi tạo. - Xác định phạm vi tệp đính kèm:
- Quy trình quảng bá: Khi đề xuất tạo khoá truy cập ngay sau khi đăng nhập bằng mật khẩu chuẩn hoặc quảng bá sau khi đăng ký, hãy đặt
authenticatorAttachment: "platform"để thực thi trình xác thực nền tảng và bỏ qua lời nhắc khoá bảo mật bên ngoài. - Quy trình quản lý: Khi được gọi từ một bảng điều khiển bảo mật hoặc chế độ cài đặt chuyên dụng, nơi hỗ trợ khoá bảo mật bên ngoài ngoài trình xác thực nền tảng, hãy bỏ qua hoàn toàn thuộc tính
authenticatorAttachment. - Mẹo: Chấp nhận cờ yêu cầu
promotion: booleanđể xử lý có điều kiện cả hai luồng bằng một điểm cuối duy nhất.
- Quy trình quảng bá: Khi đề xuất tạo khoá truy cập ngay sau khi đăng nhập bằng mật khẩu chuẩn hoặc quảng bá sau khi đăng ký, hãy đặt
// Options generation example
const options = {
challenge: serverGeneratedBase64UrlChallenge, // Cryptographically random challenge
rp: { id: "example.com", name: "Secure Application" },
user: {
id: userBase64UrlId, // Unique base64url string identifying the account
name: "user@example.com",
displayName: "Jane Doe",
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7,
},
{
type: "public-key",
alg: -257,
},
],
excludeCredentials: userExistingCredentials.map((cred) => ({
type: "public-key",
id: cred.id,
transports: cred.transports,
})),
authenticatorSelection: {
residentKey: "required",
requireResidentKey: true,
userVerification: "preferred",
...(isPromotionFlow && { authenticatorAttachment: "platform" }),
},
};
Xác minh
- Xác minh thử thách: Xác minh thử thách một cách an toàn dựa trên thử thách dự kiến liên kết với phiên.
- Xác minh sự hiện diện của người dùng:
- Đảm bảo rằng cờ Người dùng hiện diện (UP) được trả về trong dữ liệu trình xác thực đã phân tích cú pháp là
trueđể xác nhận sự hiện diện thực tế của người dùng tại thời điểm tạo.
- Đảm bảo rằng cờ Người dùng hiện diện (UP) được trả về trong dữ liệu trình xác thực đã phân tích cú pháp là
- Nới lỏng quy trình xác minh cho "preferred":
- Khi các tuỳ chọn tạo được chỉ định
userVerification: "preferred", lệnh gọi xác minh phía máy chủ PHẢI được định cấu hình bằngrequireUserVerification: false. Nếu không, trình xác thực đăng ký mà không có quy trình xác minh người dùng (ví dụ: tắt phương thức khoá màn hình) sẽ kích hoạt lỗi xác minh máy chủ giả mạo.
- Khi các tuỳ chọn tạo được chỉ định
Logic phía máy khách
- Kiểm soát giao diện người dùng khi tải trang:
- Khi tải trang, hãy gọi
PublicKeyCredential.getClientCapabilities()và tắt nút "Tạo khoá truy cập" nếu không cóconditionalGethoặcpasskeyPlatformAuthenticator.
- Khi tải trang, hãy gọi
- Gọi lệnh tạo và chuyển đổi tuần tự: Giải mã các tuỳ chọn máy chủ bằng
PublicKeyCredential.parseCreationOptionsFromJSON()và truyền cấu hình thu được đếnnavigator.credentials.create().- Gọi
credential.toJSON()để mã hoáAuthenticatorAttestationResponsethành một đối tượng hợp lệ, có thể chuyển đổi tuần tự JSON trước khi tìm nạp điểm cuối xác minh.
- Gọi
- Xử lý các trường hợp ngoại lệ của WebAuthn:
InvalidStateError: Đã có khoá truy cập trùng khớp (doexcludeCredentialsso khớp).NotAllowedError: Người dùng đã huỷ hoặc hết thời gian chờ hộp thoại xác thực khoá truy cập.AbortError: Thao tác đã bị huỷ.SecurityError: Lỗi nguồn gốc bảo mật (HTTPS) hoặc lỗi không khớp mã nhận dạng bên tin cậy (vấn đề về cấu hình).
- Phân tách Try/Catch cho Signal API:
- Gói lệnh gọi xác minh máy chủ
fetch()trong một khối try/catch. GọisignalUnknownCredential()khi không tìm nạp được thông tin xác minh máy chủ (bất kỳ trạng tháiresponse.ok === falsehoặc lỗi mạng nào).
- Gói lệnh gọi xác minh máy chủ
// optionsFetch and registerVerifyFetch are app-defined HTTP methods
import { optionsFetch, registerVerifyFetch } from "./api.js";
async function registerPasskey(isPromotion = false) {
// Verify passkey capability and conditional UI are available
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (
!capabilities.passkeyPlatformAuthenticator ||
!capabilities.conditionalGet
) {
// Hide "Create passkey" buttons and fall back to password flows instead
showStandardPasswordFallbackUI();
return;
}
const creationOptionsJSON = await optionsFetch({ promotion: isPromotion });
const publicKey =
PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);
let credential;
try {
// passkey prompt execution
credential = await navigator.credentials.create({ publicKey });
} catch (err) {
if (err.name === "InvalidStateError") {
console.log("A passkey already exists for this account.");
alert("A passkey already exists for this account.");
} else if (err.name === "SecurityError") {
console.error("Configuration RP ID or Secure Context error.");
alert("Configuration RP ID or Secure Context error.");
} else if (err.name === "NotAllowedError") {
console.log("User cancelled the passkey dialog.");
} else if (err.name === "AbortError") {
console.log("The creation operation has been aborted.");
}
return; // Safe API exit, do not signal unknown for standard WebAuthn cancels
}
// Server Verification phase (Segregated Try/Catch)
let encodedResponse = credential.toJSON();
try {
const response = await registerVerifyFetch(encodedResponse);
if (!response.ok) {
// Server verification failed to verify/authenticate the credential (orphaned)
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encodedResponse.id, // Base64URL-encoded credential ID
});
}
} catch (serverErr) {
console.error("Server verification network failure:", serverErr);
await publickeycredential.signalunknowncredential({
rpId, // RP ID must match the one defined on the server
credentialid: encodedresponse.id, // base64url-encoded credential id
});
}
}
Chiến lược dự phòng
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: Nếu trình duyệt không hỗ trợ
PublicKeyCredential.signalUnknownCredential, thì lệnh gọi PHẢI được bỏ qua một cách an toàn thông qua cổng phát hiện tính năng (if (PublicKeyCredential.signalUnknownCredential)) và quy trình xác minh phía máy chủ chỉ ghi lại lỗi mà không kích hoạt bản cập nhật của trình quản lý.
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.parseCreationOptionsFromJSON và PublicKeyCredential.prototype.toJSON cũng được hỗ trợ.
import 'webauthn-polyfills';