Tạo khoá truy cập cho các lần đă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.

Việc sử dụng khoá truy cập thay vì mật khẩu là một cách hay để các trang web giúp tài khoản người dùng của họ an toàn, đơn giả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 chỉ cần sử dụng vân tay, khuôn mặt hoặc mã PIN của thiết bị để đăng nhập vào một trang web hoặc ứng dụng.

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

Cách thức hoạt động

Người dùng có thể được yêu cầu tạo khoá truy cập trong 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 (tức là authenticatorAttachmentcross-platform).
  • Trên một trang chuyên biệt để người dùng có thể quản lý khoá truy cập của họ.

Để tạo khoá truy cập, hãy 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 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 có chức năng 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

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

  1. Một 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 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 một khoá truy cập. Lệnh 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 ý 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ợ rồi lưu trữ mã xác thực cũng như khoá công khai liên kết với tài khoản người dùng cho các lần 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ợ, nhưng vẫn có một số điểm thiếu nhỏ. Hãy tham khảo bài viết Hỗ trợ thiết bị – khoá.dev để tìm hiểu xem tổ hợp trình duyệt và hệ điều hành nào hỗ trợ việc tạo khoá truy cập.

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

Dưới đây là cách giao diện người dùng sẽ hoạt độ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  
    }  
  });  
}  

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

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, 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 thử thách do máy chủ tạo trong ArrayBuffer cho lượt đăng ký này. Đây là thuộc tính bắt buộc nhưng không được sử dụng trong quá trình đăng ký, trừ phi bạn thực hiện việc chứng thực – một chủ đề nâng cao mà chúng tôi không đề 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 bao gồm thông tin nhận dạng cá nhân, ví dụ: địa chỉ e-mail hoặc tên người dùng. Một giá trị ngẫu nhiên, 16 byte đượ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 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 của họ. Tên này sẽ hiển thị trong bộ chọn tài khoản. (Nếu sử dụng tên người dùng, hãy sử dụng cùng một giá trị như trong quy trình xác thực bằng mật khẩu.)
  • user.displayName: Trường nà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ần phải là tên duy nhất và có thể là tên do 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ị trên bộ chọn tài khoản tuỳ thuộc vào trình duyệt.
  • excludeCredentials: Ngăn đăng ký cùng một thiết bị bằng cách cung cấp danh sách mã nhận dạng 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 một khoá truy cập mới. API trả về một lời hứa, chờ 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 mọi miền con trên example.com.

  • rp.name: Tên của RP.

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

  • authenticatorSelection.authenticatorAttachment: Đặt giá trị này thành "platform" nếu quá trình tạo khoá truy cập này là bản nâng cấp từ một mật khẩu, ví dụ như trong một chương trình khuyến mãi sau khi đăng nhập. "platform" cho biết rằng RP muốn 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) 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 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ể phát hiện (khoá thường trú) 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 hoạt động 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", tức là trình xác thực có thể bỏ qua bước 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ị, một khoá truy cập sẽ được tạo và lời hứa sẽ được phân giải, sẽ 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. 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 đã tồn tại trên thiết bị. Người dùng sẽ không nhìn thấy hộp thoại báo lỗi nào và trang web không nên coi đây là lỗi — người dùng muốn thiết bị cục bộ đã được đăng ký.
  • NotAllowedError: Người dùng đã huỷ thao tác.
  • Cá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ột mã nhận dạng được 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 khoá truy cập phù hợp có trong 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 trên phần phụ trợ.
  • rawId: Một phiên bản ArrayBuffer của mã thông tin xác thực.
  • response.clientDataJSON: Dữ liệu ứng dụng được mã hoá bằng ArrayBuffer.
  • response.attestationObject: Đố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 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 tới phần phụ trợ sau khi mã hoá một phần bằng base64url.

AuthenticatorAttestationResponse

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

Khi nhận được thông tin xác thực khoá công khai trên phần phụ trợ, hãy truyền thông tin đó vào 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ố cơ sở lưu trú điển hình cần 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 các thông tin sau đây mà bạn nên lưu trong cơ sở dữ liệu:

Để xác thực người dùng, hãy đọc phần Đă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