ย้ายข้อมูลจาก FIDO2 ไปยังเครื่องมือจัดการข้อมูลเข้าสู่ระบบ

Credential Manager เป็น API ที่แนะนำสำหรับการตรวจสอบสิทธิ์ใน Android ซึ่งรองรับพาสคีย์, การลงชื่อเข้าใช้แบบรวมศูนย์ และผู้ให้บริการตรวจสอบสิทธิ์บุคคลที่สาม โดยมีสภาพแวดล้อมที่ปลอดภัยและสะดวกสบายซึ่งช่วยให้ผู้ใช้ซิงค์และจัดการข้อมูลเข้าสู่ระบบได้ สำหรับนักพัฒนาแอปที่ใช้ข้อมูลเข้าสู่ระบบ FIDO2 ในเครื่อง คุณควรอัปเดตแอปให้รองรับการตรวจสอบสิทธิ์ด้วยพาสคีย์โดยผสานรวมกับ Credential Manager API เอกสารนี้อธิบายวิธีย้ายข้อมูลโปรเจ็กต์จาก FIDO2 ไปยัง Credential Manager

เหตุผลที่ควรย้ายข้อมูลจาก FIDO2 ไปยัง Credential Manager

ในกรณีส่วนใหญ่ คุณควรย้ายข้อมูลผู้ให้บริการตรวจสอบสิทธิ์ของแอป Android ไปยัง Credential Manager เหตุผลที่ควรย้ายข้อมูลไปยัง Credential Manager ได้แก่

  • การรองรับพาสคีย์: Credential Manager รองรับพาสคีย์ ซึ่งเป็นกลไกการตรวจสอบสิทธิ์แบบใหม่ที่ไม่ต้องใช้รหัสผ่าน ซึ่งปลอดภัยและใช้งานง่ายกว่ารหัสผ่าน
  • วิธีการลงชื่อเข้าใช้หลายวิธี: Credential Manager รองรับวิธีการลงชื่อเข้าใช้หลายวิธี ซึ่งรวมถึงรหัสผ่าน พาสคีย์ และวิธีการลงชื่อเข้าใช้แบบรวมศูนย์ ซึ่งจะช่วยให้ผู้ใช้ตรวจสอบสิทธิ์ในแอปได้ง่ายขึ้น ไม่ว่าจะใช้วิธีการตรวจสอบสิทธิ์ที่ต้องการแบบใดก็ตาม
  • การรองรับผู้ให้บริการข้อมูลเข้าสู่ระบบของบุคคลที่สาม: ใน Android 14 ขึ้นไป Credential Manager รองรับผู้ให้บริการข้อมูลเข้าสู่ระบบของบุคคลที่สามหลายราย ซึ่งหมายความว่าผู้ใช้สามารถใช้ข้อมูลเข้าสู่ระบบที่มีอยู่จากผู้ให้บริการรายอื่นเพื่อลงชื่อเข้าใช้แอปของคุณได้
  • ประสบการณ์ของผู้ใช้ที่สอดคล้องกัน: Credential Manager มอบประสบการณ์ของผู้ใช้ที่สอดคล้องกันมากขึ้นสำหรับการตรวจสอบสิทธิ์ในแอปและกลไกการลงชื่อเข้าใช้ต่างๆ ซึ่งจะช่วยให้ผู้ใช้เข้าใจและใช้ขั้นตอนการตรวจสอบสิทธิ์ของแอปได้ง่ายขึ้น

หากต้องการเริ่มการย้ายข้อมูลจาก FIDO2 ไปยัง Credential Manager ให้ทำตามขั้นตอนต่อไปนี้

อัปเดตทรัพยากร Dependency

  1. อัปเดตปลั๊กอิน Kotlin ใน build.gradle ของโปรเจ็กต์เป็นเวอร์ชัน 1.8.10 ขึ้นไป

      plugins {
        //…
          id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
        //…
      }
    
  2. ใน build.gradle ของโปรเจ็กต์ ให้อัปเดตทรัพยากร Dependency เพื่อใช้ไลบรารี Credential Manager และบริการ Google Play Authentication เวอร์ชันล่าสุด

      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 lower:
        implementation 'androidx.credentials:credentials-play-services-auth:<latest-version>'
        // ...
      }
    
  3. แทนที่การเริ่มต้น FIDO ด้วยการเริ่มต้น Credential Manager เพิ่มการประกาศนี้ในคลาสที่คุณใช้สำหรับวิธีการสร้างพาสคีย์และการลงชื่อเข้าใช้

    val credMan = CredentialManager.create(context)
    

สร้างพาสคีย์

คุณจะต้องสร้างพาสคีย์ใหม่ เชื่อมโยงกับบัญชีของผู้ใช้ และจัดเก็บคีย์สาธารณะของพาสคีย์ไว้ในเซิร์ฟเวอร์ก่อนที่ผู้ใช้จะลงชื่อเข้าใช้ด้วยพาสคีย์ดังกล่าวได้ ตั้งค่าแอปให้มีความสามารถนี้โดยอัปเดตการเรียกใช้ฟังก์ชันการลงทะเบียน

รูปที่ 1 ภาพนี้แสดงวิธีแลกเปลี่ยนข้อมูลระหว่างแอปและเซิร์ฟเวอร์เมื่อสร้างพาสคีย์โดยใช้ Credential Manager
  1. หากต้องการรับพารามิเตอร์ที่จำเป็นซึ่งส่งไปยังเมธอด createCredential() ระหว่างการสร้างพาสคีย์ ให้เพิ่ม name("residentKey").value("required") (ตามที่อธิบายไว้ในข้อกำหนดเฉพาะของ WebAuthn) ลงในการเรียกใช้เซิร์ฟเวอร์ registerRequest()

    suspend fun registerRequest() {
        // ...
        val call = client.newCall(
            Builder()
                .method(
                    "POST",
                    jsonRequestBody {
                        name("attestation").value("none")
                        name("authenticatorSelection").objectValue {
                            name("residentKey").value("required")
                        }
                    }
                ).build()
        )
        // ...
    }
    
  2. ตั้งค่าประเภท return สำหรับ registerRequest() และฟังก์ชันย่อยทั้งหมดเป็น JSONObject

    suspend fun registerRequest(sessionId: String): ApiResult<JSONObject> {
        val call = client.newCall(
            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. นำเมธอดที่จัดการการเรียก Intent Launcher และผลลัพธ์ของกิจกรรมออกจากมุมมองของคุณอย่างปลอดภัย

  4. เนื่องจากตอนนี้ registerRequest() แสดงผลเป็น JSONObject คุณจึงไม่จำเป็นต้องสร้าง PendingIntent แทนที่ Intent ที่แสดงผลด้วย JSONObject อัปเดตการเรียกใช้ Intent Launcher เพื่อเรียกใช้ createCredential() จาก Credential Manager API เรียกใช้เมธอด API createCredential()

    suspend fun createPasskey(
        activity: Activity,
        requestResult: JSONObject
    ): CreatePublicKeyCredentialResponse? {
        val request = CreatePublicKeyCredentialRequest(requestResult.toString())
        var response: CreatePublicKeyCredentialResponse? = null
        try {
            response = credMan.createCredential(
                request = request as CreateCredentialRequest,
                context = activity
            ) as CreatePublicKeyCredentialResponse
        } catch (e: CreateCredentialException) {
    
            showErrorAlert(activity, e)
    
            return null
        }
        return response
    }
    
  5. เมื่อการเรียกใช้สำเร็จ ให้ส่งการตอบกลับกลับไปยังเซิร์ฟเวอร์ คำขอและการตอบกลับสำหรับการเรียกนี้คล้ายกับการใช้งาน FIDO2 จึงไม่จำเป็นต้องทำการเปลี่ยนแปลงใดๆ

ตรวจสอบสิทธิ์ด้วยพาสคีย์

หลังจากตั้งค่าการสร้างพาสคีย์แล้ว คุณสามารถตั้งค่าแอปเพื่ออนุญาตให้ผู้ใช้ลงชื่อเข้าใช้และตรวจสอบสิทธิ์โดยใช้พาสคีย์ได้ โดยคุณจะต้องอัปเดตโค้ดการตรวจสอบสิทธิ์เพื่อจัดการผลลัพธ์ของ Credential Manager และใช้ฟังก์ชันเพื่อตรวจสอบสิทธิ์ผ่านพาสคีย์

รูปที่ 2 ขั้นตอนการตรวจสอบสิทธิ์ด้วยพาสคีย์ของ Credential Manager
  1. การเรียกคำขอลงชื่อเข้าใช้ไปยังเซิร์ฟเวอร์เพื่อรับข้อมูลที่จำเป็นซึ่งจะส่งไปยังคำขอ getCredential() จะเหมือนกับการใช้งาน FIDO2 คุณไม่จำเป็นต้องทำการเปลี่ยนแปลงใดๆ
  2. การตอบกลับที่แสดงผลจะอยู่ในรูปแบบ 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. นำเมธอดที่จัดการ Intent Launcher และการเรียกใช้ผลลัพธ์ของกิจกรรมออกจากมุมมองของคุณอย่างปลอดภัย

  4. เนื่องจากตอนนี้ signInRequest() แสดงผลเป็น JSONObject คุณจึงไม่จำเป็นต้องสร้าง PendingIntent แทนที่ Intent ที่แสดงผลด้วย JSONObject และเรียกใช้ getCredential() จากเมธอด 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. เมื่อการเรียกสำเร็จแล้ว ให้ส่งการตอบกลับกลับไปยังเซิร์ฟเวอร์เพื่อตรวจสอบและตรวจสอบสิทธิ์ผู้ใช้ พารามิเตอร์คำขอและการตอบกลับสำหรับการเรียก API นี้คล้ายกับการติดตั้งใช้งาน FIDO2 จึงไม่จำเป็นต้องทำการเปลี่ยนแปลงใดๆ

แหล่งข้อมูลเพิ่มเติม