Di chuyển từ FIDO2 sang Trình quản lý thông tin xác thực

Với khả năng hỗ trợ khoá truy cập, dịch vụ đăng nhập liên kết và trình cung cấp xác thực bên thứ ba, Trình quản lý thông tin xác thực là API được đề xuất cho hoạt động xác thực trên Android, giúp mang đến một môi trường an toàn, thuận tiện để người dùng có thể đồng bộ hoá và quản lý thông tin xác thực của họ. Đối với các nhà phát triển sử dụng thông tin xác thực FIDO2 trên máy, bạn nên cập nhật ứng dụng của mình để hỗ trợ hình thức xác thực bằng khoá truy cập thông qua việc tích hợp với API Trình quản lý thông tin xác thực. Tài liệu này mô tả cách di chuyển dự án của bạn từ FIDO2 sang Trình quản lý thông tin xác thực.

Lý do bạn nên chuyển từ FIDO2 sang Trình quản lý thông tin xác thực

Trong hầu hết các trường hợp, bạn nên di chuyển từ trình cung cấp dịch vụ xác thực của ứng dụng Android sang Trình quản lý thông tin xác thực: Sau đây là những lý do bạn nên di chuyển sang Trình quản lý thông tin xác thực:

  • Hỗ trợ khoá truy cập: Trình quản lý thông tin xác thực hỗ trợ khoá truy cập. Đây là một cơ chế xác thực mới, không cần mật khẩu, an toàn và dễ sử dụng hơn so với mật khẩu.
  • Nhiều phương thức đăng nhập: Trình quản lý thông tin xác thực hỗ trợ nhiều phương thức đăng nhập, trong đó có mật khẩu, khoá truy cập và phương thức đăng nhập liên kết. Điều này giúp người dùng xác thực với ứng dụng của bạn dễ dàng hơn, bất kể phương thức xác thực họ ưu tiên là gì.
  • Hỗ trợ trình cung cấp thông tin xác thực bên thứ ba: Trên Android 14 trở lên, Trình quản lý thông tin xác thực hỗ trợ nhiều trình cung cấp thông tin xác thực bên thứ ba. Điều này có nghĩa là người dùng có thể sử dụng thông tin xác thực hiện có của họ từ các trình cung cấp khác để đăng nhập vào ứng dụng của bạn.
  • Trải nghiệm người dùng nhất quán: Trình quản lý thông tin xác thực mang đến một trải nghiệm nhất quán hơn cho người dùng khi xác thực trên các ứng dụng và cơ chế đăng nhập. Điều này giúp người dùng hiểu và sử dụng quy trình xác thực của ứng dụng dễ dàng hơn.

Để bắt đầu quá trình di chuyển từ FIDO2 sang Trình quản lý thông tin xác thực, hãy làm theo các bước dưới đây.

Cập nhật phần phụ thuộc

  1. Cập nhật trình bổ trợ Kotlin trong build.gradle của dự án lên phiên bản 1.8.10 trở lên.

    plugins {
      //…
        id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
      //…
    }
    
  2. Trong build.gradle của dự án, hãy cập nhật các phần phụ thuộc để sử dụng Trình quản lý thông tin xác thực và tính năng Xác thực Dịch vụ Play.

    dependencies {
      // ...
      // Credential Manager:
      implementation 'androidx.credentials:credentials:<latest-version>'
    
      // Play Services Authentication:
      // Optional - needed for credentials support from play services, for devices running
      // Android 13 and below:
      implementation 'androidx.credentials:credentials-play-services-auth:<latest-version>'
      // ...
    }
    
  3. Thay thế quy trình khởi động FIDO bằng quy trình khởi động Trình quản lý thông tin xác thực. Thêm phần khai báo này vào lớp mà bạn dùng để tạo khoá truy cập và phương thức đăng nhập:

    val credMan = CredentialManager.create(context)
    

Tạo khoá truy cập

Bạn cần tạo một khoá truy cập mới, liên kết khoá đó với một tài khoản của người dùng và lưu trữ khoá công khai của khoá truy cập đó 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 đó. Thiết lập tính năng này cho ứng dụng của bạn bằng cách cập nhật các lệnh gọi hàm register.

Hình 1. Hình này minh hoạ cách trao đổi dữ liệu giữa ứng dụng và máy chủ khi một khoá truy cập được tạo bằng Trình quản lý thông tin xác thực.
  1. Để lấy các tham số cần thiết đã được gửi đến phương thức createCredential() trong quá trình tạo khoá truy cập, hãy thêm name("residentKey").value("required") như mô tả trong phần thông số kỹ thuật WebAuthn vào lệnh gọi máy chủ registerRequest().

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .method("POST", jsonRequestBody {
            name("attestation").value("none")
            name("authenticatorSelection").objectValue {
                name("residentKey").value("required")
            }
        }).build()
        // ...
    }
    
  2. Đặt kiểu return cho registerRequest() và đặt tất cả các hàm con thành JSONObject.

    suspend fun registerRequest(sessionId: String): ApiResult<JSONObject> {
        val call = client.newCall(
            Request.Builder()
                .url("$BASE_URL/<your api url>")
                .addHeader("Cookie", formatCookie(sessionId))
                .method("POST", jsonRequestBody {
                    name("attestation").value("none")
                    name("authenticatorSelection").objectValue {
                        name("authenticatorAttachment").value("platform")
                        name("userVerification").value("required")
                        name("residentKey").value("required")
                    }
                }).build()
        )
        val response = call.await()
        return response.result("Error calling the api") {
            parsePublicKeyCredentialCreationOptions(
                body ?: throw ApiException("Empty response from the api call")
            )
        }
    }
    
  3. Yên tâm xoá mọi phương thức giúp xử lý lệnh gọi kết quả hoạt động và trình chạy ý định từ khung hiển thị của bạn.

  4. registerRequest() hiện trả về một JSONObject, nên bạn không cần tạo PendingIntent. Thay thế ý định được trả về bằng JSONObject. Cập nhật các lệnh gọi của trình chạy ý định để gọi createCredential() từ API Trình quản lý thông tin xác thực. Gọi phương thức API createCredential().

    suspend fun createPasskey(
        activity: Activity,
        requestResult: JSONObject
        ): CreatePublicKeyCredentialResponse? {
            val request = CreatePublicKeyCredentialRequest(requestResult.toString())
            var response: CreatePublicKeyCredentialResponse? = null
            try {
                response = credMan.createCredential(
                    request as CreateCredentialRequest,
                    activity
                ) as CreatePublicKeyCredentialResponse
            } catch (e: CreateCredentialException) {
    
                showErrorAlert(activity, e)
    
                return null
            }
            return response
        }
    
  5. Sau khi lệnh gọi có trạng thái thành công, hãy gửi lại phản hồi này cho máy chủ. Yêu cầu và phản hồi cho lệnh gọi này giống như khi bạn triển khai FIDO2, vì vậy, bạn không cần thay đổi gì.

Xác thực bằng khoá truy cập

Sau khi thiết lập tính năng tạo khoá truy cập, bạn có thể thiết lập ứng dụng của mình sao cho người dùng có thể đăng nhập và xác thực bằng khoá truy cập của họ. Để làm vậy, bạn sẽ cập nhật mã xác thực dùng để xử lý các kết quả của Trình quản lý thông tin xác thực và triển khai một hàm để xác thực thông qua khoá truy cập.

Hình 2. Quy trình xác thực khoá truy cập của Trình quản lý thông tin xác thực.
  1. Lệnh gọi yêu cầu đăng nhập của bạn đến máy chủ để lấy thông tin cần thiết được gửi tới yêu cầu getCredential() cũng giống như khi bạn triển khai FIDO2. Do đó, bạn không cần thay đổi gì.
  2. Tương tự như lệnh gọi yêu cầu đăng ký, phản hồi được trả về có định dạng JSONObject.

    /**
     * @param sessionId The session ID to be used for the sign-in.
     * @param credentialId The credential ID of this device.
     * @return a JSON object.
     */
    suspend fun signinRequest(): ApiResult<JSONObject> {
        val call = client.newCall(Builder().url(buildString {
            append("$BASE_URL/signinRequest")
        }).method("POST", jsonRequestBody {})
            .build()
        )
        val response = call.await()
        return response.result("Error calling /signinRequest") {
            parsePublicKeyCredentialRequestOptions(
                body ?: throw ApiException("Empty response from /signinRequest")
            )
        }
    }
    
    /**
     * @param sessionId The session ID to be used for the sign-in.
     * @param response The JSONObject for signInResponse.
     * @param credentialId id/rawId.
     * @return A list of all the credentials registered on the server,
     * including the newly-registered one.
     */
    suspend fun signinResponse(
        sessionId: String, response: JSONObject, credentialId: String
        ): ApiResult<Unit> {
    
            val call = client.newCall(
                Builder().url("$BASE_URL/signinResponse")
                    .addHeader("Cookie",formatCookie(sessionId))
                    .method("POST", jsonRequestBody {
                        name("id").value(credentialId)
                        name("type").value(PUBLIC_KEY.toString())
                        name("rawId").value(credentialId)
                        name("response").objectValue {
                            name("clientDataJSON").value(
                                response.getString("clientDataJSON")
                            )
                            name("authenticatorData").value(
                                response.getString("authenticatorData")
                            )
                            name("signature").value(
                                response.getString("signature")
                            )
                            name("userHandle").value(
                                response.getString("userHandle")
                            )
                        }
                    }).build()
            )
            val apiResponse = call.await()
            return apiResponse.result("Error calling /signingResponse") {
            }
        }
    
  3. Yên tâm xoá mọi phương thức giúp xử lý lệnh gọi kết quả hoạt động và trình chạy ý định từ khung hiển thị của bạn.

  4. signInRequest() hiện trả về một JSONObject, nên bạn không cần tạo PendingIntent. Thay thế ý định được trả về bằng JSONObject và gọi getCredential() từ các phương thức API.

    suspend fun getPasskey(
        activity: Activity,
        creationResult: JSONObject
        ): GetCredentialResponse? {
            Toast.makeText(
                activity,
                "Fetching previously stored credentials",
                Toast.LENGTH_SHORT)
                .show()
            var result: GetCredentialResponse? = null
            try {
                val request= GetCredentialRequest(
                    listOf(
                        GetPublicKeyCredentialOption(
                            creationResult.toString(),
                            null
                        ),
                        GetPasswordOption()
                    )
                )
                result = credMan.getCredential(activity, request)
                if (result.credential is PublicKeyCredential) {
                    val publicKeycredential = result.credential as PublicKeyCredential
                    Log.i("TAG", "Passkey ${publicKeycredential.authenticationResponseJson}")
                    return result
                }
            } catch (e: Exception) {
                showErrorAlert(activity, e)
            }
            return result
        }
    
  5. Sau khi lệnh gọi có trạng thái thành công, hãy gửi lại phản hồi cho máy chủ để kiểm tra và xác thực người dùng. Các tham số cho yêu cầu và phản hồi của lệnh gọi API này cũng giống như khi bạn triển khai FIDO2, nên bạn không cần thay đổi gì.

Tài nguyên khác