FIDO2'den Kimlik Bilgisi Yöneticisi'ne geçme

Kimlik Bilgisi Yöneticisi; geçiş anahtarları, birleşik oturum açma ve üçüncü taraf kimlik doğrulama sağlayıcılarına yönelik destek sunan Android'de kimlik doğrulama için önerilen API'dir. Bu API, kullanıcıların kimlik bilgilerini senkronize edip yönetmesine olanak tanıyan güvenli ve kullanışlı bir ortam sağlar. Yerel FIDO2 kimlik bilgilerini kullanan geliştiriciler, Credential Manager API ile entegre ederek geçiş anahtarı kimlik doğrulamasını desteklemek için uygulamanızı güncellemelidir. Bu belgede, projenizi FIDO2'den Kimlik Bilgisi Yöneticisi'ne nasıl taşıyacağınız açıklanmaktadır.

FIDO2'den Kimlik Bilgisi Yöneticisi'ne geçiş nedenleri

Çoğu durumda, Android uygulamanızın kimlik doğrulama sağlayıcısını Kimlik Bilgisi Yöneticisi'ne taşımanız gerekir. Kimlik Bilgisi Yöneticisi'ne taşınma nedenleri şunlardır:

  • Geçiş anahtarı desteği: Kimlik Bilgisi Yöneticisi, daha güvenli ve kullanımı şifrelere göre daha kolay olan yeni ve şifresiz bir kimlik doğrulama mekanizması olan geçiş anahtarlarını destekler.
  • Çoklu oturum açma yöntemleri: Kimlik Bilgisi Yöneticisi şifreler, geçiş anahtarları ve birleşik oturum açma yöntemleri dahil çoklu oturum açma yöntemlerini destekler. Bu, tercih ettikleri kimlik doğrulama yönteminden bağımsız olarak kullanıcıların uygulamanızda kimlik doğrulaması yapmasını kolaylaştırır.
  • Üçüncü taraf kimlik bilgisi sağlayıcı desteği: Android 14 ve sonraki sürümlerde Kimlik Bilgisi Yöneticisi birden fazla üçüncü taraf kimlik bilgisi sağlayıcısını destekler. Yani kullanıcılarınız, uygulamanızda oturum açmak için diğer sağlayıcılardan aldığı mevcut kimlik bilgilerini kullanabilir.
  • Tutarlı kullanıcı deneyimi: Kimlik Bilgisi Yöneticisi, uygulamalar ve oturum açma mekanizmaları arasında kimlik doğrulama için daha tutarlı bir kullanıcı deneyimi sağlar. Bu, kullanıcıların uygulamanızın kimlik doğrulama akışını anlamalarını ve kullanmalarını kolaylaştırır.

FIDO2'den Kimlik Bilgisi Yöneticisi'ne taşıma işlemine başlamak için aşağıdaki adımları uygulayın.

Bağımlıları güncelleme

  1. Projenizin build.gradle dosyasındaki Kotlin eklentisini 1.8.10 veya üzeri bir sürüme güncelleyin.

    plugins {
      //…
        id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
      //…
    }
    
  2. Projenizin build.gradle dosyasında bağımlılıklarınızı, Kimlik Bilgisi Yöneticisi ve Play Hizmetleri Kimlik Doğrulaması'nı kullanacak şekilde güncelleyin.

    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. FIDO başlatma işlemini, Kimlik Bilgisi Yöneticisi başlatma ile değiştirin. Bu beyanı, geçiş anahtarı oluşturma ve oturum açma yöntemleri için kullandığınız sınıfa ekleyin:

    val credMan = CredentialManager.create(context)
    

Geçiş anahtarı oluşturun

Kullanıcının oturum açabilmesi için yeni bir geçiş anahtarı oluşturmanız, bunu bir kullanıcı hesabıyla ilişkilendirmeniz ve geçiş anahtarının ortak anahtarını sunucunuzda saklamanız gerekir. Kayıt işlevi çağrılarını güncelleyerek uygulamanızı bu özellikle kurun.

Şekil 1. Bu şekilde, Kimlik Bilgisi Yöneticisi kullanılarak bir geçiş anahtarı oluşturulduğunda uygulama ile sunucu arasında veri alışverişinin nasıl yapıldığı gösterilmektedir.
  1. Geçiş anahtarı oluşturma sırasında createCredential() yöntemine gönderilen gerekli parametreleri almak için registerRequest() sunucusu çağrınıza WebAuthn spesifikasyonunda açıklandığı gibi name("residentKey").value("required") ekleyin)

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .method("POST", jsonRequestBody {
            name("attestation").value("none")
            name("authenticatorSelection").objectValue {
                name("residentKey").value("required")
            }
        }).build()
        // ...
    }
    
  2. registerRequest() için return türünü ve tüm alt işlevleri JSONObject olarak ayarlayın.

    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. Amaç başlatıcı ve etkinlik sonucu çağrılarını işleyen tüm yöntemleri görünümünüzden güvenli bir şekilde kaldırın.

  4. registerRequest() artık JSONObject döndürdüğünden PendingIntent oluşturmanız gerekmez. Döndürülen amacı bir JSONObject ile değiştirin. Amaç başlatıcı çağrılarınızı Authentication Manager API'den createCredential() çağrısı yapacak şekilde güncelleyin. createCredential() API yöntemini çağırın.

    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. Çağrı başarılı olduktan sonra yanıtı sunucuya gönderin. Bu çağrının isteği ve yanıtı FIDO2 uygulamasına benzer. Bu nedenle herhangi bir değişiklik gerekmez.

Geçiş anahtarlarıyla kimlik doğrulayın

Geçiş anahtarı oluşturmayı ayarladıktan sonra uygulamanızı, kullanıcıların geçiş anahtarı kullanarak oturum açıp kimlik doğrulaması yapmasına izin verecek şekilde ayarlayabilirsiniz. Bunun için kimlik doğrulama kodunuzu Kimlik Bilgisi Yöneticisi sonuçlarını işleyecek ve geçiş anahtarları aracılığıyla kimlik doğrulama işlevi uygulayacak şekilde güncelleyin.

Şekil 2. Kimlik Bilgisi Yöneticisi'nin geçiş anahtarı kimlik doğrulama akışı.
  1. getCredential() isteğine gönderilecek gerekli bilgileri almak için sunucuya yaptığınız oturum açma isteği çağrısı, FIDO2 uygulamasıyla aynıdır. Değişiklik gerekmez.
  2. Kayıt isteği çağrısına benzer şekilde, döndürülen yanıt JSONObject biçimindedir.

    /**
     * @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. Amaç başlatıcı ve etkinlik sonucu çağrılarını işleyen tüm yöntemleri görünümünüzden güvenli bir şekilde kaldırın.

  4. signInRequest() artık JSONObject döndürdüğünden PendingIntent oluşturmanız gerekmez. Döndürülen amacı bir JSONObject ile değiştirin ve API yöntemlerinizden getCredential() yöntemini çağırın.

    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. Çağrı başarılı olduktan sonra, kullanıcının kimliğini doğrulamak ve kimliğini doğrulamak için yanıtı sunucuya geri gönderin. Bu API çağrısı için istek ve yanıt parametreleri, FIDO2 uygulamasına benzer. Bu nedenle herhangi bir değişiklik gerekmez.

Ek kaynaklar