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

Geçiş anahtarları, birleşik oturum açma ve üçüncü taraf kimlik doğrulama sağlayıcıları desteğiyle Kimlik Bilgisi Yöneticisi, 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, uygulamalarını Credential Manager API ile entegrasyon yaparak geçiş anahtarı kimlik doğrulamasını destekleyecek şekilde güncellemelidir. Bu dokümanda, 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çmenin 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 geçmenin nedenleri şunlardır:

  • Geçiş anahtarı desteği: Kimlik Bilgisi Yöneticisi, şifrelerden daha güvenli ve kullanımı daha kolay olan yeni bir şifresiz kimlik doğrulama mekanizması olan geçiş anahtarlarını destekler.
  • Birden fazla oturum açma yöntemi: Kimlik Bilgisi Yöneticisi, şifreler, geçiş anahtarları ve birleşik oturum açma yöntemleri dahil olmak üzere birden fazla oturum açma yöntemini destekler. Bu sayede kullanıcılar, tercih ettikleri kimlik doğrulama yönteminden bağımsız olarak uygulamanızda kimlik doğrulaması yapabilir.
  • Üçü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ıyı destekler. Bu, kullanıcılarınızın uygulamanızda oturum açmak için diğer sağlayıcılardaki mevcut kimlik bilgilerini kullanabileceği anlamına gelir.
  • Tutarlı kullanıcı deneyimi: Kimlik Bilgisi Yöneticisi, uygulamalarda ve oturum açma mekanizmaları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şlemini başlatmak için aşağıdaki adımları uygulayın.

Bağımlılıkları güncelleme

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

    plugins {
      //…
        id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
      //…
    }
    
  2. Projenizin derleme.gradle dosyasında, Kimlik Bilgisi Yöneticisi ve Play Hizmetleri Kimlik Doğrulaması'nı kullanmak için bağımlılıklarınızı 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 ilklendirmesini, Kimlik Bilgisi Yöneticisi ilklendirmesiyle değiştirin. Geçiş anahtarı oluşturma ve oturum açma yöntemleri için kullandığınız sınıfa şu beyanı ekleyin:

    val credMan = CredentialManager.create(context)
    

Geçiş anahtarı oluşturma

Kullanıcının bu anahtarla oturum açabilmesi için yeni bir geçiş anahtarı oluşturmanız, bu anahtarı kullanıcının hesabıyla ilişkilendirmeniz ve geçiş anahtarının herkese açık anahtarını sunucunuzda saklamanız gerekir. register işlev çağrılarını güncelleyerek uygulamanızı bu özellikle ayarlayın.

Şekil 1. Bu şekilde, Kimlik Bilgisi Yöneticisi kullanılarak geçiş anahtarı oluşturulduğunda uygulama ile sunucu arasında nasıl veri alışverişi yapılacağı gösterilmektedir.
  1. Geçiş anahtarı oluşturulurken createCredential() yöntemine gönderilen gerekli parametreleri almak için registerRequest() sunucu çağrınıza WebAuthn spesifikasyonunda açıklandığı şekilde 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() ve tüm alt işlevler için return türünü 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. Intent 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 bir JSONObject döndürdüğünden PendingIntent oluşturmanız gerekmez. Döndürülen intent'i JSONObject ile değiştirin. Amaç başlatıcı çağrılarınızı Kimlik Bilgisi Yöneticisi API'sinden 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ı olduğunda yanıtı sunucuya geri gönderin. Bu çağrının isteği ve yanıtı, FIDO2 uygulamasına benzer olduğundan herhangi bir değişiklik gerekmez.

Geçiş anahtarlarıyla kimlik doğrulama

Geçiş anahtarı oluşturma özelliğini ayarladıktan sonra uygulamanızı, kullanıcıların geçiş anahtarlarını kullanarak oturum açmasına ve kimlik doğrulamasına izin verecek şekilde ayarlayabilirsiniz. Bunu yapmak için kimlik doğrulama kodunuzu, Kimlik Bilgisi Yöneticisi sonuçlarını işleyecek şekilde güncelleyin ve geçiş anahtarları aracılığıyla kimlik doğrulama yapacak bir işlev uygulayın.

Şekil 2. Kimlik Bilgisi Yöneticisi'nin geçiş anahtarı kimlik doğrulama akışı.
  1. getCredential() isteğine gönderilecek gerekli bilgilerin alınması için sunucuya yaptığınız oturum açma isteği çağrınız, FIDO2 uygulamasıyla aynı. Herhangi bir değişiklik yapılması 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. Intent başlatıcıyı 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 intent'i JSONObject ile değiştirin ve getCredential()'yi API yöntemlerinizden ç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. Arama başarılı olduktan sonra kullanıcıyı doğrulamak ve kimliğini doğrulamak için yanıtı sunucuya geri gönderin. Bu API çağrısının istek ve yanıt parametreleri FIDO2 uygulamasına benzer olduğundan herhangi bir değişiklik yapılması gerekmez.

Ek kaynaklar