Tìm hiểu chuyên sâu về thông tin xác thực có thể phát hiện

Mặc dù thông tin xác thực FIDO (chẳng hạn như khoá truy cập) nhằm mục đích thay thế mật khẩu, nhưng hầu hết thông tin xác thực này cũng có thể giúp người dùng không cần nhập tên người dùng. Điều này cho phép người dùng xác thực bằng cách chọn một tài khoản trong danh sách khoá truy cập mà họ có cho trang web hiện tại.

Các phiên bản khoá bảo mật trước đây được thiết kế dưới dạng phương thức xác thực 2 bước và yêu cầu mã nhận dạng của thông tin xác thực tiềm năng, do đó, người dùng phải nhập tên người dùng. Thông tin xác thực mà khoá bảo mật có thể tìm thấy mà không cần biết mã nhận dạng của thông tin xác thực đó được gọi là thông tin xác thực có thể phát hiện. Hầu hết thông tin xác thực FIDO được tạo ngày nay đều là thông tin xác thực có thể phát hiện, đặc biệt là khoá truy cập được lưu trữ trong trình quản lý mật khẩu hoặc trên khoá bảo mật hiện đại.

Để đảm bảo thông tin xác thực được tạo dưới dạng khoá truy cập (thông tin xác thực có thể phát hiện), hãy chỉ định residentKeyrequireResidentKey khi tạo thông tin xác thực.

Các bên tin cậy (RP) có thể sử dụng thông tin xác thực có thể phát hiện bằng cách bỏ qua allowCredentials trong quá trình xác thực khoá truy cập. Trong những trường hợp này, trình duyệt hoặc hệ thống sẽ hiển thị cho người dùng danh sách các khoá truy cập hiện có, được xác định bằng thuộc tính user.name được đặt tại thời điểm tạo. Nếu người dùng chọn một khoá truy cập, thì giá trị user.id sẽ được đưa vào chữ ký kết quả. Sau đó, máy chủ có thể sử dụng giá trị đó hoặc mã nhận dạng thông tin xác thực được trả về để tra cứu tài khoản thay vì tên người dùng đã nhập.

Giao diện người dùng của bộ chọn tài khoản (như những giao diện đã thảo luận trước đó) không bao giờ hiển thị thông tin xác thực không thể phát hiện.

requireResidentKeyresidentKey

Để tạo khoá truy cập, hãy chỉ định authenticatorSelection.residentKeyauthenticatorSelection.requireResidentKey trên navigator.credentials.create() với các giá trị được chỉ định như sau.

async function register () {
  // ...

  const publicKeyCredentialCreationOptions = {
    // ...
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      residentKey: 'required',
      requireResidentKey: true,
    }
  };

  const credential = await navigator.credentials.create({
    publicKey: publicKeyCredentialCreationOptions
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;

  // ...
}

residentKey:

  • 'required': Phải tạo thông tin xác thực có thể phát hiện. Nếu không tạo được, NotSupportedError sẽ được trả về.
  • 'preferred': Bên tin cậy ưu tiên tạo thông tin xác thực có thể phát hiện, nhưng chấp nhận thông tin xác thực không thể phát hiện.
  • 'discouraged': Bên tin cậy ưu tiên tạo thông tin xác thực không thể phát hiện, nhưng chấp nhận thông tin xác thực có thể phát hiện.

requireResidentKey:

  • Thuộc tính này được giữ lại để tương thích ngược với WebAuthn Cấp 1, một phiên bản cũ hơn của thông số kỹ thuật. Đặt thuộc tính này thành true nếu residentKey'required', nếu không, hãy đặt thành false.

allowCredentials

Các bên tin cậy có thể sử dụng allowCredentials trên navigator.credentials.get() để kiểm soát trải nghiệm xác thực khoá truy cập. Thường có 3 loại trải nghiệm xác thực khoá truy cập:

Với thông tin xác thực có thể phát hiện, các bên tin cậy có thể hiển thị bộ chọn tài khoản dạng thức để người dùng chọn một tài khoản để đăng nhập, sau đó xác minh người dùng. Điều này phù hợp với quy trình xác thực khoá truy cập được bắt đầu bằng cách nhấn một nút dành riêng cho việc xác thực khoá truy cập.

Để đạt được trải nghiệm người dùng này, hãy bỏ qua hoặc truyền một mảng trống cho tham số allowCredentials trong navigator.credentials.get().

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // You can omit `allowCredentials` as well:
    allowCredentials: []
  };

  const credential = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}

Hiển thị tính năng tự động điền biểu mẫu khoá truy cập

Bộ chọn tài khoản dạng thức được mô tả ở trên hoạt động tốt nếu hầu hết người dùng sử dụng khoá truy cập và có sẵn khoá truy cập trên thiết bị cục bộ. Đối với người dùng không có khoá truy cập cục bộ, hộp thoại dạng thức vẫn xuất hiện và sẽ đề nghị người dùng xuất trình khoá truy cập từ một thiết bị khác. Trong khi chuyển người dùng sang khoá truy cập, bạn có thể muốn tránh giao diện người dùng đó cho những người dùng chưa thiết lập khoá truy cập.

Thay vào đó, việc chọn khoá truy cập có thể được gộp vào lời nhắc tự động điền cho các trường trong biểu mẫu đăng nhập truyền thống, cùng với tên người dùng và mật khẩu đã lưu. Bằng cách này, người dùng có khoá truy cập có thể "điền" biểu mẫu đăng nhập bằng cách chọn khoá truy cập của họ, người dùng có cặp tên người dùng/mật khẩu đã lưu có thể chọn các cặp đó và người dùng không có cặp nào vẫn có thể nhập tên người dùng và mật khẩu.

Trải nghiệm người dùng này là lý tưởng khi bên tin cậy đang trong quá trình di chuyển với việc sử dụng hỗn hợp mật khẩu và khoá truy cập.

Để đạt được trải nghiệm người dùng này, ngoài việc truyền một mảng trống cho thuộc tính allowCredentials hoặc bỏ qua tham số, hãy chỉ định mediation: 'conditional' trên navigator.credentials.get() và chú thích trường nhập dữ liệu username HTML bằng autocomplete="username webauthn" hoặc trường nhập dữ liệu password bằng autocomplete="password webauthn".

Lệnh gọi đến navigator.credentials.get() sẽ không khiến bất kỳ giao diện người dùng nào được hiển thị, nhưng nếu người dùng tập trung vào trường nhập dữ liệu được chú thích, thì mọi khoá truy cập hiện có sẽ được đưa vào các lựa chọn tự động điền. Nếu người dùng chọn một khoá truy cập, họ sẽ trải qua quy trình xác minh mở khoá thiết bị thông thường và chỉ khi đó, lời hứa do .get() trả về mới phân giải bằng một kết quả. Nếu người dùng không chọn khoá truy cập, thì lời hứa sẽ không bao giờ phân giải.

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // You can omit `allowCredentials` as well:
    allowCredentials: []
  };

  const cred = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal,
    // Specify 'conditional' to activate conditional UI
    mediation: 'conditional'
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}
<input type="text" name="username" autocomplete="username webauthn" ...>

Bạn có thể tìm hiểu cách tạo trải nghiệm người dùng này tại 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, cũng như lớp học lập trình Triển khai khoá truy cập bằng tính năng tự động điền biểu mẫu trong ứng dụng web.

Xác thực lại

Trong một số trường hợp (chẳng hạn như khi sử dụng khoá truy cập để xác thực lại), mã nhận dạng của người dùng đã được biết. Trong trường hợp này, chúng tôi muốn sử dụng khoá truy cập mà không cần trình duyệt hoặc hệ điều hành hiển thị bất kỳ dạng nào của bộ chọn tài khoản. Bạn có thể thực hiện việc này bằng cách truyền danh sách mã nhận dạng thông tin xác thực trong tham số allowCredentials.

Trong trường hợp đó, nếu có bất kỳ thông tin xác thực nào được đặt tên có sẵn cục bộ, thì người dùng sẽ được nhắc mở khoá thiết bị ngay lập tức. Nếu không, người dùng sẽ được nhắc xuất trình một thiết bị khác (điện thoại hoặc khoá bảo mật) có chứa thông tin xác thực hợp lệ.

Để đạt được trải nghiệm người dùng này, hãy cung cấp danh sách mã nhận dạng thông tin xác thực cho người dùng đăng nhập. Bên tin cậy phải có thể truy vấn các mã nhận dạng đó vì người dùng đã được biết. Cung cấp mã nhận dạng thông tin xác thực dưới dạng đối tượng PublicKeyCredentialDescriptor trong thuộc tính allowCredentials trong navigator.credentials.get().

async function authenticate() {
  // ...

  const publicKeyCredentialRequestOptions = {
    // Server generated challenge:
    challenge: ****,
    // The same RP ID as used during registration:
    rpId: 'example.com',
    // Provide a list of PublicKeyCredentialDescriptors:
    allowCredentials: [{
      id: ****,
      type: 'public-key',
      transports: [
        'internal',
        'hybrid'
      ]
    }, {
      id: ****,
      type: 'public-key',
      transports: [
        'internal',
        'hybrid'
      ]
    }, ...]
  };

  const credential = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions,
    signal: abortController.signal
  });

  // This does not run until the user selects a passkey.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;
  
  // ...
}

Đối tượng PublicKeyCredentialDescriptor bao gồm:

  • id: Mã nhận dạng của thông tin xác thực khoá công khai mà bên tin cậy đã nhận được khi đăng ký khoá truy cập.
  • type: Trường này thường là 'public-key'.
  • transports: Gợi ý về các phương thức truyền tải được thiết bị chứa thông tin xác thực này hỗ trợ, được trình duyệt sử dụng để tối ưu hoá giao diện người dùng yêu cầu người dùng xuất trình thiết bị bên ngoài. Danh sách này (nếu được cung cấp) phải chứa kết quả của việc gọi getTransports() trong quá trình đăng ký từng thông tin xác thực.

Tóm tắt

Thông tin xác thực có thể phát hiện giúp trải nghiệm đăng nhập bằng khoá truy cập trở nên thân thiện hơn nhiều với người dùng bằng cách cho phép họ bỏ qua việc nhập tên người dùng. Với sự kết hợp của residentKey, requireResidentKeyallowCredentials, các bên tin cậy có thể đạt được trải nghiệm đăng nhập:

  • Hiển thị bộ chọn tài khoản dạng thức.
  • Hiển thị tính năng tự động điền biểu mẫu khoá truy cập.
  • Xác thực lại.

Hãy sử dụng thông tin xác thực có thể phát hiện một cách khôn ngoan. Bằng cách đó, bạn có thể thiết kế trải nghiệm đăng nhập bằng khoá truy cập tinh vi mà người dùng sẽ thấy liền mạch và có nhiều khả năng tương tác hơn.