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ı için destek sunan Kimlik Bilgisi Yöneticisi, Android'de kimlik doğrulama için önerilen API'dir. Kullanıcıların kimlik bilgilerini senkronize edip yönetebileceği güvenli ve kullanışlı bir ortam sağlar. Yerel FIDO2 kimlik bilgilerini kullanan geliştiriciler, uygulamalarını Credential Manager API ile entegre ederek geçiş anahtarı kimlik doğrulamasını destekleyecek şekilde 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ş yapma 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çiş yapmanın nedenleri şunlardır:

  • Geçiş anahtarı desteği: Kimlik Bilgisi Yöneticisi, şifrelerden daha güvenli ve kullanımı daha kolay olan yeni, ş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 olmak üzere çoklu oturum açma yöntemlerini destekler. Bu sayede kullanıcılar, tercih ettikleri kimlik doğrulama yönteminden bağımsız olarak uygulamanızda daha kolay 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, uygulamalar ve oturum açma mekanizmaları genelinde kimlik doğrulama için daha tutarlı bir kullanıcı deneyimi sunar. Bu sayede kullanıcılar, uygulamanızın kimlik doğrulama akışını daha kolay anlayıp kullanabilir.

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

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

  1. Projenizin build.gradle dosyasındaki 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 build.gradle dosyasında, bağımlılıklarınızı Kimlik Bilgisi Yöneticisi ve Google Play Hizmetleri Kimlik Doğrulama kitaplıklarının en yeni sürümlerini 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 lower:
        implementation 'androidx.credentials:credentials-play-services-auth:<latest-version>'
        // ...
      }
    
  3. FIDO başlatma işlemini, Kimlik Bilgisi Yöneticisi başlatma işlemiyle değiştirin. Geçiş anahtarı oluşturma ve oturum açma yöntemleri için kullandığınız sınıfa bu bildirimi ekleyin:

    val credMan = CredentialManager.create(context)
    

Geçiş anahtarı oluşturma

Kullanıcının geçiş anahtarıyla oturum açabilmesi için yeni bir geçiş anahtarı oluşturmanız, bunu kullanıcının hesabıyla ilişkilendirmeniz ve geçiş anahtarının ortak anahtarını sunucunuzda depolamanız gerekir. Kayıt işlevi çağrılarını güncelleyerek uygulamanızı bu özellikten yararlanacak şekilde 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ıldığı gösterilmektedir.
  1. Geçiş anahtarı oluşturma sırasında createCredential() yöntemine gönderilen gerekli parametreleri almak için registerRequest() sunucu çağrınıza name("residentKey").value("required") (WebAuthn spesifikasyonunda açıklandığı şekilde) ekleyin.

    suspend fun registerRequest() {
        // ...
        val call = client.newCall(
            Builder()
                .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(
            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üğü için PendingIntent oluşturmanız gerekmez. Döndürülen niyeti JSONObject ile değiştirin. Intent başlatıcı çağrılarınızı, Credential 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 = request as CreateCredentialRequest,
                context = activity
            ) as CreatePublicKeyCredentialResponse
        } catch (e: CreateCredentialException) {
    
            showErrorAlert(activity, e)
    
            return null
        }
        return response
    }
    
  5. Çağrı başarılı olduktan sonra 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 yapılması gerekmez.

Geçiş anahtarlarıyla kimlik doğrulama

Geçiş anahtarı oluşturma işlemini 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üncelleyecek ve geçiş anahtarlarıyla kimlik doğrulaması yapacak bir işlev uygulayacaksınız.

Şekil 2. Kimlik Bilgisi Yöneticisi'nin geçiş anahtarı kimlik doğrulama akışı.
  1. Oturum açma isteğinizin, getCredential() isteğine gönderilecek gerekli bilgileri almak için sunucuya yaptığı çağrı, FIDO2 uygulamasıyla aynıdır. 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ı 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 bir JSONObject döndürdüğünden PendingIntent oluşturmanız gerekmez. Döndürülen intent'i 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 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