Tổng quan
Sau đây là thông tin tổng quan về các bước chính trong quy trình xác thực bằng khoá truy cập:

- Xác định thử thách và các lựa chọn khác cần thiết để xác thực bằng khoá truy cập. Gửi các thông tin này đến ứng dụng để bạn có thể chuyển chúng đến lệnh gọi xác thực khoá truy cập (
navigator.credentials.gettrên web). Sau khi người dùng xác nhận xác thực bằng khoá truy cập, lệnh gọi xác thực bằng khoá truy cập sẽ được giải quyết và trả về thông tin xác thực (PublicKeyCredential). Thông tin xác thực này chứa một xác nhận xác thực.
- Xác minh câu nhận định xác thực.
- Nếu câu nhận định xác thực là hợp lệ, hãy xác thực người dùng.
Các phần sau đây sẽ đi sâu vào chi tiết của từng bước.
Tạo thử thách
Trong thực tế, thử thách là một mảng byte ngẫu nhiên, được biểu thị dưới dạng đối tượng ArrayBuffer.
// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8
Để đảm bảo thử thách đạt được mục đích, bạn phải:
- Đảm bảo không bao giờ sử dụng cùng một thử thách nhiều lần. Tạo một thử thách mới cho mỗi lần đăng nhập. Huỷ bỏ thử thách sau mỗi lần đăng nhập, bất kể thành công hay thất bại. Đồng thời, hãy loại bỏ thử thách sau một khoảng thời gian nhất định. Đừng bao giờ chấp nhận cùng một thử thách nhiều lần trong một phản hồi.
- Đảm bảo thử thách được bảo mật bằng mật mã. Thử thách phải là một câu hỏi mà người khác gần như không thể đoán được. Để tạo một thử thách bảo mật bằng mật mã phía máy chủ, tốt nhất bạn nên dựa vào thư viện phía máy chủ FIDO mà bạn tin tưởng. Nếu bạn tự tạo thử thách, hãy sử dụng chức năng mật mã tích hợp sẵn trong bộ phần mềm cơ sở của bạn hoặc tìm các thư viện được thiết kế cho các trường hợp sử dụng mật mã. Ví dụ: iso-crypto trong Node.js hoặc secrets trong Python. Theo quy cách, thử thách phải dài ít nhất 16 byte để được coi là an toàn.
Sau khi tạo một thử thách, hãy lưu thử thách đó vào phiên của người dùng để xác minh sau.
Tạo các lựa chọn yêu cầu thông tin xác thực
Tạo các lựa chọn yêu cầu thông tin xác thực dưới dạng đối tượng publicKeyCredentialRequestOptions.
Để làm việc này, hãy dựa vào thư viện phía máy chủ FIDO. Thư viện này thường cung cấp một hàm hiệu dụng có thể tạo các tuỳ chọn này cho bạn. Ví dụ: SimpleWebAuthn cung cấp generateAuthenticationOptions.
publicKeyCredentialRequestOptions phải chứa tất cả thông tin cần thiết để xác thực bằng khoá truy cập. Truyền thông tin này đến hàm trong thư viện phía máy chủ FIDO chịu trách nhiệm tạo đối tượng publicKeyCredentialRequestOptions.
Một số trường của publicKeyCredentialRequestOptions có thể là hằng số. Các giá trị khác phải được xác định linh động trên máy chủ:
rpId: Mã nhận dạng bên tin cậy mà bạn muốn liên kết với thông tin xác thực, ví dụ:example.com. Quá trình xác thực sẽ chỉ thành công nếu mã nhận dạng bên Relying Party mà bạn cung cấp ở đây khớp với mã nhận dạng bên Relying Party được liên kết với thông tin xác thực. Để điền mã nhận dạng bên tin cậy, hãy sử dụng cùng một giá trị với mã nhận dạng bên tin cậy mà bạn đặt trongpublicKeyCredentialCreationOptionstrong quá trình đăng ký thông tin xác thực.challenge: Một phần dữ liệu mà nhà cung cấp khoá truy cập sẽ ký để chứng minh người dùng giữ khoá truy cập tại thời điểm yêu cầu xác thực. Xem thông tin chi tiết trong phần Tạo thử thách.allowCredentials: Một mảng thông tin xác thực được chấp nhận cho quy trình xác thực này. Truyền một mảng trống để cho phép người dùng chọn một khoá truy cập có sẵn trong danh sách do trình duyệt hiển thị. Hãy xem phần Tìm nạp thử thách từ máy chủ RP và Tìm hiểu sâu về thông tin xác thực có thể phát hiện để biết thông tin chi tiết.userVerification: Cho biết liệu quy trình xác minh người dùng bằng phương thức khoá màn hình của thiết bị là "bắt buộc", "ưu tiên" hay "không khuyến khích". Xem phần Tìm nạp thử thách từ máy chủ RP.timeout: Thời gian (tính bằng mili giây) mà người dùng có thể thực hiện để hoàn tất quy trình xác thực. Thời gian này phải đủ dài và ngắn hơn thời gian hoạt động củachallenge. Giá trị mặc định được đề xuất là 5 phút, nhưng bạn có thể tăng lên đến 10 phút, vẫn nằm trong phạm vi được đề xuất. Thời gian chờ dài là hợp lý nếu bạn muốn người dùng sử dụng quy trình kết hợp, thường mất nhiều thời gian hơn một chút. Nếu thao tác hết thời gian chờ,NotAllowedErrorsẽ được gửi.
Sau khi bạn tạo publicKeyCredentialRequestOptions, hãy gửi tệp đó cho khách hàng.
challenge quá trình giải mã diễn ra ở phía máy khách.Mã ví dụ: tạo các lựa chọn cho yêu cầu thông tin xác thực
Chúng tôi sử dụng thư viện SimpleWebAuthn trong các ví dụ của mình. Tại đây, chúng ta chuyển việc tạo các tuỳ chọn yêu cầu thông tin xác thực sang hàm generateAuthenticationOptions.
import {
generateAuthenticationOptions,
} from '@simplewebauthn/server';
router.post('/signinRequest', csrfCheck, async (req, res) => {
// Ensure you nest calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Use the generateAuthenticationOptions function from SimpleWebAuthn
const options = await generateAuthenticationOptions({
rpID: process.env.HOSTNAME,
allowCredentials: [],
});
// Save the challenge in the user session
req.session.challenge = options.challenge;
return res.json(options);
} catch (e) {
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Xác minh và đăng nhập cho người dùng
Khi navigator.credentials.get phân giải thành công trên ứng dụng, đối tượng PublicKeyCredential sẽ được trả về.
navigator.credentials.get trả về một PublicKeyCredential.response là một AuthenticatorAssertionResponse. Nó đại diện cho phản hồi của nhà cung cấp khoá truy cập đối với hướng dẫn của ứng dụng khách để tạo những gì cần thiết nhằm thử và xác thực bằng khoá truy cập trên RP. Bảng này gồm có:
response.authenticatorDatavàresponse.clientDataJSON, chẳng hạn như ở bước đăng ký khoá truy cập.response.signaturechứa chữ ký trên các giá trị này.
Gửi đối tượng PublicKeyCredential đến máy chủ.
Trên máy chủ, hãy làm như sau:
- Thu thập thông tin cần thiết để xác minh câu nhận định và xác thực người dùng:
- Lấy thử thách dự kiến mà bạn đã lưu trữ trong phiên khi tạo các tuỳ chọn xác thực.
- Lấy nguồn gốc và mã nhận dạng bên tin cậy dự kiến.
- Tìm người dùng trong cơ sở dữ liệu của bạn. Trong trường hợp thông tin xác thực có thể phát hiện, bạn không biết người dùng nào đang gửi yêu cầu xác thực. Để tìm hiểu, bạn có hai lựa chọn:
- Cách 1: Sử dụng
response.userHandletrong đối tượngPublicKeyCredential. Trong bảng Người dùng, hãy tìmpasskey_user_idkhớp vớiuserHandle. - Cách 2: Sử dụng thông tin xác thực
idcó trong đối tượngPublicKeyCredential. Trong bảng Thông tin xác thực khoá công khai, hãy tìm thông tin xác thựcidkhớp với thông tin xác thựcidcó trong đối tượngPublicKeyCredential. Sau đó, hãy tìm người dùng tương ứng bằng cách sử dụng khoá ngoạipasskey_user_idcho bảng Người dùng.
- Cách 1: Sử dụng
- Tìm trong cơ sở dữ liệu của bạn thông tin xác thực khoá công khai khớp với câu nhận định xác thực mà bạn nhận được. Để làm việc này, trong bảng Thông tin xác thực khoá công khai, hãy tìm thông tin xác thực
idkhớp với thông tin xác thựcidcó trong đối tượngPublicKeyCredential.
Xác minh câu nhận định xác thực. Chuyển bước xác minh này sang thư viện phía máy chủ FIDO, thường sẽ cung cấp một hàm hiệu dụng cho mục đích này. Ví dụ: SimpleWebAuthn cung cấp
verifyAuthenticationResponse. Tìm hiểu những gì đang diễn ra trong Phụ lục: xác minh phản hồi xác thực.Xoá thử thách cho dù quá trình xác minh có thành công hay không để ngăn chặn các cuộc tấn công lặp lại.
Đăng nhập người dùng. Nếu xác minh thành công, hãy cập nhật thông tin phiên để đánh dấu người dùng là đã đăng nhập. Bạn cũng nên trả về một đối tượng
usercho ứng dụng khách để giao diện người dùng có thể sử dụng thông tin liên kết với người dùng mới đăng nhập.
Mã ví dụ: xác minh và đăng nhập người dùng
Chúng tôi sử dụng thư viện SimpleWebAuthn trong các ví dụ của mình. Tại đây, chúng ta chuyển giao việc xác minh phản hồi xác thực cho hàm verifyAuthenticationResponse.
import {
verifyAuthenticationResponse,
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
router.post('/signinResponse', csrfCheck, async (req, res) => {
const response = req.body;
const expectedChallenge = req.session.challenge;
const expectedOrigin = getOrigin(req.get('User-Agent'));
const expectedRPID = process.env.HOSTNAME;
// Ensure you nest verification function calls in try/catch blocks.
// If something fails, throw an error with a descriptive error message.
// Return that message with an appropriate error code to the client.
try {
// Find the credential stored to the database by the credential ID
const cred = Credentials.findById(response.id);
if (!cred) {
throw new Error('Credential not found.');
}
// Find the user - Here alternatively we could look up the user directly
// in the Users table via userHandle
const user = Users.findByPasskeyUserId(cred.passkey_user_id);
if (!user) {
throw new Error('User not found.');
}
// Prepare credential information for verification
const credential = {
id: cred.id,
publicKey: isoBase64URL.toBuffer(cred.publicKey),
transports: cred.transports,
};
// Verify the credential
const { verified, authenticationInfo } = await verifyAuthenticationResponse({
response,
expectedChallenge,
expectedOrigin,
expectedRPID,
credential,
requireUserVerification: false,
});
if (!verified) {
throw new Error('User verification failed.');
}
// Kill the challenge for this session.
delete req.session.challenge;
req.session.username = user.username;
req.session['signed-in'] = 'yes';
return res.json(user);
} catch (e) {
delete req.session.challenge;
console.error(e);
return res.status(400).json({ error: e.message });
}
});
Phụ lục: xác minh phản hồi xác thực
Việc xác minh phản hồi xác thực bao gồm các bước kiểm tra sau:
- Đảm bảo rằng mã RP khớp với trang web của bạn.
- Đảm bảo rằng nguồn gốc của yêu cầu khớp với nguồn gốc đăng nhập của trang web. Đối với ứng dụng Android, hãy xem bài viết Xác minh nguồn gốc.
- Kiểm tra để đảm bảo thiết bị có thể cung cấp thử thách mà bạn đã đưa ra.
- Xác minh rằng trong quá trình xác thực, người dùng đã tuân thủ các yêu cầu mà bạn đặt ra với tư cách là Bên phụ thuộc (RP). Nếu bạn yêu cầu xác minh người dùng, hãy đảm bảo rằng cờ
uv(người dùng đã xác minh) trongauthenticatorDatalàtrue. Kiểm tra để đảm bảo cờup(người dùng hiện diện) trongauthenticatorDatalàtrue, vì khoá truy cập luôn yêu cầu sự hiện diện của người dùng. - Xác minh chữ ký. Để xác minh chữ ký, bạn cần:
- Chữ ký, tức là thử thách đã ký:
response.signature - Khoá công khai, để xác minh chữ ký.
- Dữ liệu đã ký ban đầu. Đây là dữ liệu có chữ ký cần được xác minh.
- Thuật toán mã hoá được dùng để tạo chữ ký.
- Chữ ký, tức là thử thách đã ký:
Để tìm hiểu thêm về các bước này, hãy xem mã nguồn của SimpleWebAuthn cho verifyAuthenticationResponse hoặc tìm hiểu danh sách đầy đủ các quy trình xác minh trong quy cách.