Tạo khoá truy cập cho hoạt động đăng nhập không cần mật khẩu

Khoá truy cập giúp tài khoản người dùng an toàn, đơn giản và dễ sử dụng hơn.

Sử dụng khoá truy cập thay vì mật khẩu là một cách tuyệt vời để các trang web giúp tài khoản người dùng của họ an toàn hơn, đơn giản hơn, dễ sử dụng hơn và không cần mật khẩu. Với khoá truy cập, người dùng có thể đăng nhập vào một trang web hoặc ứng dụng chỉ bằng cách sử dụng vân tay, khuôn mặt hoặc mã PIN của thiết bị.

Bạn phải tạo khoá truy cập, liên kết với tài khoản người dùng và lưu trữ khoá công khai trên máy chủ của mình thì người dùng mới có thể đăng nhập bằng khoá truy cập đó.

Cách hoạt động

Người dùng có thể được yêu cầu tạo khoá truy cập vào một trong các trường hợp sau:

  • Khi người dùng đăng nhập bằng mật khẩu.
  • Khi người dùng đăng nhập bằng khoá truy cập trên một thiết bị khác (nghĩa là authenticatorAttachmentcross-platform).
  • Trên một trang riêng, nơi người dùng có thể quản lý khoá truy cập của họ.

Để tạo khoá truy cập, bạn sử dụng API WebAuthn.

4 thành phần của quy trình đăng ký khoá truy cập là:

  • Phần phụ trợ: Máy chủ phụ trợ lưu giữ cơ sở dữ liệu của tài khoản, lưu trữ khoá công khai và siêu dữ liệu khác về khoá truy cập.
  • Giao diện người dùng: Giao diện người dùng giao tiếp với trình duyệt và gửi yêu cầu tìm nạp đến phần phụ trợ.
  • Trình duyệt: Trình duyệt của người dùng đang chạy JavaScript của bạn.
  • Authenticator: Trình xác thực của người dùng, giúp tạo và lưu trữ khoá truy cập. Tệp này có thể ở trên cùng một thiết bị với trình duyệt (ví dụ: khi sử dụng Windows Hello) hoặc trên một thiết bị khác, chẳng hạn như điện thoại.
Sơ đồ đăng ký khoá truy cập

Dưới đây là quy trình thêm khoá truy cập mới vào tài khoản người dùng hiện có:

  1. Người dùng đăng nhập vào trang web.
  2. Sau khi đăng nhập, người dùng yêu cầu tạo khoá truy cập trên giao diện người dùng, chẳng hạn như bằng cách nhấn vào nút "Tạo khoá truy cập".
  3. Giao diện người dùng yêu cầu thông tin từ phần phụ trợ để tạo khoá truy cập, chẳng hạn như thông tin người dùng, thử thách và mã thông tin xác thực cần loại trừ.
  4. Giao diện người dùng gọi navigator.credentials.create() để tạo khoá truy cập. Cuộc gọi này trả về một lời hứa.
  5. Khoá truy cập được tạo sau khi người dùng đồng ý bằng cách sử dụng phương thức khoá màn hình của thiết bị. Lời hứa được phân giải và thông tin xác thực khoá công khai được trả về giao diện người dùng.
  6. Giao diện người dùng gửi thông tin xác thực khoá công khai đến phần phụ trợ, đồng thời lưu trữ mã nhận dạng thông tin xác thực cũng như khoá công khai được liên kết với tài khoản người dùng để xác thực trong tương lai.

Khả năng tương thích

WebAuthn được hầu hết các trình duyệt hỗ trợ, tuy nhiên, chỉ có một số điểm khác biệt nhỏ. Hãy tham khảo bài viết Hỗ trợ thiết bị – khoá truy cập.dev để tìm hiểu tổ hợp trình duyệt và hệ điều hành hỗ trợ việc tạo khoá truy cập.

Tạo khoá truy cập mới

Dưới đây là cách hoạt động của giao diện người dùng theo yêu cầu tạo khoá truy cập mới.

Phát hiện tính năng

Trước khi hiện nút "Tạo khoá truy cập mới", hãy kiểm tra xem:

// 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  
    }  
  });  
}  

Cho đến khi bạn đáp ứng tất cả điều kiện, khoá truy cập sẽ không được hỗ trợ trên trình duyệt này. Đến lúc đó, nút "Tạo khoá truy cập mới" sẽ không xuất hiện.

Tìm nạp thông tin quan trọng từ phần phụ trợ

Khi người dùng nhấp vào nút này, hãy tìm nạp thông tin quan trọng để gọi navigator.credentials.create() từ phần phụ trợ:

  • challenge: Một yêu cầu do máy chủ tạo trong ArrayBuffer cho lượt đăng ký này. Đây là tính năng bắt buộc nhưng không được sử dụng trong quá trình đăng ký, trừ phi thực hiện chứng thực – một chủ đề nâng cao không được đề cập ở đây.
  • user.id: Mã nhận dạng duy nhất của người dùng. Giá trị này phải là một ArrayBuffer không chứa thông tin nhận dạng cá nhân, ví dụ: địa chỉ email hoặc tên người dùng. Một giá trị 16 byte ngẫu nhiên được tạo cho mỗi tài khoản sẽ hoạt động tốt.
  • user.name: Trường này phải chứa một giá trị nhận dạng duy nhất của tài khoản mà người dùng sẽ nhận ra, chẳng hạn như địa chỉ email hoặc tên người dùng. Thông tin này sẽ xuất hiện trong bộ chọn tài khoản. (Nếu sử dụng tên người dùng, hãy sử dụng giá trị giống như khi xác thực mật khẩu.)
  • user.displayName: Đây là tên bắt buộc và thân thiện với người dùng hơn của tài khoản. Tên này không được là duy nhất và có thể là tên người dùng đã chọn. Nếu trang web của bạn không có giá trị phù hợp để đưa vào đây, hãy chuyển một chuỗi trống. Thông tin này có thể hiển thị trong bộ chọn tài khoản, tuỳ thuộc vào trình duyệt.
  • excludeCredentials: Ngăn việc đăng ký cùng một thiết bị bằng cách cung cấp một danh sách mã thông tin xác thực đã đăng ký. Thành phần transports (nếu được cung cấp) phải chứa kết quả của lệnh gọi getTransports() trong quá trình đăng ký từng thông tin xác thực.

Gọi API WebAuthn để tạo khoá truy cập

Gọi navigator.credentials.create() để tạo khoá truy cập mới. API trả về một lời hứa, chờ hoạt động tương tác của người dùng hiển thị hộp thoại phương thức.

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.  

Các tham số không được giải thích ở trên là:

  • rp.id: Mã RP là một miền và trang web có thể chỉ định miền hoặc hậu tố có thể đăng ký. Ví dụ: nếu nguồn gốc của RP là https://login.example.com:1337, thì mã RP có thể là login.example.com hoặc example.com. Nếu mã RP được chỉ định là example.com, thì người dùng có thể xác thực trên login.example.com hoặc trên bất kỳ miền con nào trên example.com.

  • rp.name: Tên của bên bị hạn chế.

  • pubKeyCredParams: Trường này chỉ định các thuật toán khoá công khai được hỗ trợ của RP. Bạn nên đặt giá trị này thành [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]. Quy tắc này chỉ định tính năng hỗ trợ cho ECDSA với P-256 và RSA PKCS#1, đồng thời hỗ trợ các API này sẽ mang lại mức độ phù hợp đầy đủ.

  • authenticatorSelection.authenticatorAttachment: Đặt mã này thành "platform" nếu việc tạo khoá truy cập này là bản nâng cấp từ mật khẩu, chẳng hạn như trong một chương trình khuyến mãi sau khi đăng nhập. "platform" cho biết RP muốn có trình xác thực nền tảng (trình xác thực được nhúng vào thiết bị nền tảng). Trình xác thực này sẽ không nhắc chèn, chẳng hạn như khoá bảo mật USB. Người dùng có một lựa chọn đơn giản hơn để tạo một khoá truy cập.

  • authenticatorSelection.requireResidentKey: Đặt giá trị này thành giá trị boolean "true". Thông tin xác thực có thể khám phá (khoá thường trú) sẽ lưu trữ thông tin người dùng vào khoá truy cập và cho phép người dùng chọn tài khoản khi xác thực.

  • authenticatorSelection.userVerification: Cho biết liệu quá trình xác minh người dùng bằng phương thức khoá màn hình thiết bị là "required", "preferred" hay "discouraged". Giá trị mặc định là "preferred", nghĩa là trình xác thực có thể bỏ qua quy trình xác minh người dùng. Hãy đặt thuộc tính này thành "preferred" hoặc bỏ qua thuộc tính này.

Gửi thông tin xác thực khoá công khai được trả về đến phần phụ trợ

Sau khi người dùng đồng ý sử dụng phương thức khoá màn hình của thiết bị, khoá truy cập sẽ được tạo và lời hứa được phân giải và trả về một đối tượng PublicKeyCredential cho giao diện người dùng.

Lời hứa có thể bị từ chối vì nhiều lý do khác nhau. Bạn có thể xử lý các lỗi này bằng cách kiểm tra thuộc tính name của đối tượng Error:

  • InvalidStateError: Khoá truy cập đã có trên thiết bị. Người dùng sẽ không thấy hộp thoại lỗi và trang web không được xem đây là lỗi – người dùng muốn thiết bị cục bộ được đăng ký và điều đó là đúng.
  • NotAllowedError: Người dùng đã huỷ thao tác.
  • Trường hợp ngoại lệ khác: Đã xảy ra lỗi không mong muốn. Trình duyệt sẽ hiển thị hộp thoại lỗi cho người dùng.

Đối tượng thông tin xác thực khoá công khai chứa các thuộc tính sau:

  • id: Mã hoá Base64URL của khoá truy cập đã tạo. Mã nhận dạng này giúp trình duyệt xác định xem có khoá truy cập phù hợp trên thiết bị hay không khi xác thực. Giá trị này cần được lưu trữ trong cơ sở dữ liệu của phần phụ trợ.
  • rawId: Một phiên bản ArrayBuffer của mã nhận dạng thông tin xác thực.
  • response.clientDataJSON: Dữ liệu ứng dụng được mã hoá bằng ArrayBuffer.
  • response.attestationObject: Một đối tượng chứng thực được mã hoá ArrayBuffer. Tệp này chứa các thông tin quan trọng như mã RP, cờ và khoá công khai.
  • authenticatorAttachment: Trả về "platform" khi thông tin xác thực này được tạo trên một thiết bị có hỗ trợ khoá truy cập.
  • type: Trường này luôn được đặt thành "public-key".

Nếu sử dụng thư viện để xử lý đối tượng thông tin xác thực khoá công khai trên phần phụ trợ, bạn nên gửi toàn bộ đối tượng đến phần phụ trợ sau khi mã hoá một phần bằng base64url.

Lưu thông tin xác thực

Sau khi nhận được thông tin xác thực về khoá công khai trên phần phụ trợ, hãy chuyển thông tin đó đến thư viện FIDO để xử lý đối tượng.

Sau đó, bạn có thể lưu trữ thông tin được truy xuất từ thông tin xác thực vào cơ sở dữ liệu để sử dụng sau này. Danh sách sau đây bao gồm một số thuộc tính điển hình mà bạn có thể lưu:

  • Mã thông tin xác thực (Khoá chính)
  • User ID
  • Khoá công khai

Thông tin xác thực khoá công khai cũng bao gồm những thông tin sau mà bạn nên lưu trong cơ sở dữ liệu:

Để xác thực người dùng, hãy đọc bài viết Đăng nhập bằng khoá truy cập thông qua tính năng tự động điền biểu mẫu.

Tài nguyên