Bermigrasi dari FIDO2 ke Pengelola Kredensial

Dengan dukungan untuk kunci sandi, login gabungan, dan penyedia autentikasi pihak ketiga, Pengelola Kredensial adalah API yang direkomendasikan untuk autentikasi di Android yang menyediakan lingkungan yang aman dan nyaman, yang memungkinkan pengguna menyinkronkan dan mengelola kredensial mereka. Untuk developer yang menggunakan kredensial FIDO2 lokal, Anda harus mengupdate aplikasi untuk mendukung autentikasi kunci sandi melalui integrasi dengan Credential Manager API. Dokumen ini menjelaskan cara memigrasikan project Anda dari FIDO2 ke Pengelola Kredensial.

Alasan bermigrasi dari FIDO2 ke Pengelola Kredensial

Dalam sebagian besar kasus, Anda harus memigrasikan penyedia autentikasi aplikasi Android ke Pengelola Kredensial. Alasan untuk bermigrasi ke Pengelola Kredensial mencakup:

  • Dukungan kunci sandi: Pengelola Kredensial mendukung kunci sandi, yaitu mekanisme autentikasi baru tanpa sandi yang lebih aman dan lebih mudah digunakan daripada sandi.
  • Metode multi-login: Pengelola Kredensial mendukung metode multi-login, termasuk sandi, kunci sandi, dan metode login gabungan. Hal ini memudahkan pengguna untuk melakukan autentikasi ke aplikasi Anda, apa pun metode autentikasi pilihan mereka.
  • Dukungan penyedia kredensial pihak ketiga: Di Android 14 dan yang lebih baru, Pengelola Kredensial mendukung beberapa penyedia kredensial pihak ketiga. Artinya, pengguna Anda dapat menggunakan kredensial yang sudah ada dari penyedia lain untuk login ke aplikasi Anda.
  • Pengalaman pengguna yang konsisten: Pengelola Kredensial memberikan pengalaman pengguna yang lebih konsisten untuk autentikasi di berbagai aplikasi dan mekanisme login. Hal ini memudahkan pengguna untuk memahami dan menggunakan alur autentikasi aplikasi Anda.

Untuk memulai migrasi dari FIDO2 ke Pengelola Kredensial, ikuti langkah-langkah di bawah ini.

Memperbarui dependensi

  1. Update plugin Kotlin di build.gradle project Anda ke versi 1.8.10 atau yang lebih baru.

    plugins {
      //…
        id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
      //…
    }
    
  2. Dalam build.gradle project, perbarui dependensi untuk menggunakan Pengelola Kredensial dan Autentikasi Layanan 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. Ganti inisialisasi FIDO dengan inisialisasi Pengelola Kredensial. Tambahkan deklarasi ini di class yang Anda gunakan untuk metode pembuatan kunci sandi dan login:

    val credMan = CredentialManager.create(context)
    

Membuat kunci sandi

Anda harus membuat kunci sandi baru, mengaitkannya dengan akun pengguna, dan menyimpan kunci publik kunci sandi tersebut di server Anda sebelum pengguna dapat login dengan kunci tersebut. Siapkan aplikasi Anda dengan kemampuan ini dengan mengupdate panggilan fungsi register.

Gambar 1. Gambar ini menunjukkan cara data ditukarkan antara aplikasi dan server saat kunci sandi dibuat menggunakan Pengelola Kredensial.
  1. Untuk mendapatkan parameter yang diperlukan yang dikirim ke metode createCredential() selama pembuatan kunci sandi, tambahkan name("residentKey").value("required") seperti yang dijelaskan dalam spesifikasi WebAuthn ke registerRequest() panggilan server.

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .method("POST", jsonRequestBody {
            name("attestation").value("none")
            name("authenticatorSelection").objectValue {
                name("residentKey").value("required")
            }
        }).build()
        // ...
    }
    
  2. Tetapkan jenis return untuk registerRequest() dan semua fungsi turunan ke 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. Hapus dengan aman metode apa pun yang menangani peluncur intent dan panggilan hasil aktivitas dari tampilan Anda.

  4. Karena registerRequest() sekarang menampilkan JSONObject, Anda tidak perlu membuat PendingIntent. Ganti intent yang ditampilkan dengan JSONObject. Update panggilan peluncur intent Anda untuk memanggil createCredential() dari Credential Manager API. Panggil metode createCredential() API.

    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. Setelah panggilan berhasil, kirim respons kembali ke server. Permintaan dan respons untuk panggilan ini mirip dengan implementasi FIDO2, sehingga tidak diperlukan perubahan.

Mengautentikasi dengan kunci sandi

Setelah menyiapkan pembuatan kunci sandi, Anda dapat menyiapkan aplikasi untuk mengizinkan pengguna login dan melakukan autentikasi menggunakan kunci sandi mereka. Untuk melakukannya, perbarui kode autentikasi untuk menangani hasil Pengelola Kredensial dan terapkan fungsi untuk melakukan autentikasi melalui kunci sandi.

Gambar 2. Alur autentikasi kunci sandi Pengelola Kredensial.
  1. Panggilan permintaan login ke server untuk mendapatkan informasi yang diperlukan agar dikirim ke permintaan getCredential() sama dengan penerapan FIDO2. Perubahan tidak diperlukan.
  2. Mirip dengan panggilan permintaan pendaftaran, respons yang ditampilkan memiliki format 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. Hapus dengan aman metode apa pun yang menangani peluncur intent dan panggilan hasil aktivitas dari tampilan Anda.

  4. Karena signInRequest() sekarang menampilkan JSONObject, Anda tidak perlu membuat PendingIntent. Ganti intent yang ditampilkan dengan JSONObject, dan panggil getCredential() dari metode API Anda.

    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. Setelah panggilan berhasil, kirim respons kembali ke server untuk memvalidasi dan mengautentikasi pengguna. Parameter permintaan dan respons untuk panggilan API ini mirip dengan implementasi FIDO2, sehingga tidak diperlukan perubahan.

Referensi lainnya