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

Credential Manager; geçiş anahtarları, birleşik oturum açma ve üçüncü taraf kimlik doğrulama sağlayıcıları desteğiyle Android'de kimlik doğrulama için önerilen API'dir. Kullanıcılara kimlik bilgilerini senkronize etme ve yönetme olanağı tanıyan güvenli ve kullanışlı bir ortam sunar. Yerel FIDO2 kimlik bilgilerini kullanan geliştiriciler, Credential Manager API ile entegrasyon yaparak uygulamalarını geçiş anahtarı kimlik doğrulamasını destekleyecek şekilde güncellemeli. 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öneticisine 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 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 federasyon oturum açma yöntemleri dahil olmak üzere birden fazla oturum açma yöntemini destekler. Bu sayede, tercih ettikleri kimlik doğrulama yönteminden bağımsız olarak kullanıcıların uygulamanızda kimlik doğrulaması yapması kolaylaşı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ıyı destekler. Bu, kullanıcılarınızın uygulamanızda oturum açmak için diğer sağlayıcılardan aldıkları mevcut kimlik bilgilerini kullanabileceği anlamına gelir.
  • Tutarlı kullanıcı deneyimi: Kimlik Bilgisi Yöneticisi, uygulamalarda kimlik doğrulama ve oturum açma mekanizmalarında daha tutarlı bir kullanıcı deneyimi sağlar. 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şi başlatmak 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 daha yüksek bir sürüme güncelleyin.

      plugins {
        //…
          id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
        //…
      }
    
  2. Projenizin build.gradle bölümünde, bağımlılıklarınızı güncelleyerek Kimlik Bilgisi Yöneticisi ve Play Hizmetleri Kimlik Doğrulama kitaplıklarının en yeni sürümlerini kullanın.

      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şlatmayı, Kimlik Bilgisi Yöneticisi başlatmayla değiştirin. Geçiş anahtarı oluşturma ve oturum açma yöntemleri için kullandığınız sınıfa şu 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 herkese açık anahtarını sunucunuzda saklamanız gerekir. Kayıt işlevi çağrılarını güncelleyerek uygulamanızı bu özelliğe göre ayarlayın.

1. şekil. Bu şekilde, Kimlik Bilgisi Yöneticisi kullanılarak geçiş anahtarı oluşturulduğunda uygulama ile sunucu arasında verilerin nasıl değiştirildiği gösterilmektedir.
  1. Geçiş anahtarı oluşturma sırasında createCredential() yöntemine gönderilen gerekli parametreleri almak için name("residentKey").value("required") (WebAuthn spesifikasyonunda açıklandığı gibi) registerRequest() sunucu çağrınıza 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. Görünümünüzden, amaç başlatıcı ve etkinlik sonucu çağrılarını işleyen tüm yöntemleri 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ı JSONObject ile değiştirin. Niyet başlatıcı çağrılarınızı, Credential Manager API'den createCredential() çağıracak ş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. Arama başarılı olduktan sonra yanıtı sunucuya geri gönderin. Bu arama için istek 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şturmayı 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üncelleyip geçiş anahtarlarıyla kimlik doğrulama işlevi uygulayacaksınız.

Ş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. 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. Niyet 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 amacı JSONObject ile değiştirin ve API yöntemlerinizden getCredential() işlevini ç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 yanıtı sunucuya geri göndererek kullanıcıyı doğrulayın ve kimliğini onaylayın. 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