نقل البيانات من FIDO2 إلى "مدير بيانات الاعتماد"

تُعدّ واجهة برمجة التطبيقات Credential Manager API الخيار الأفضل للمصادقة على Android، إذ تتوافق مع مفاتيح المرور وتسجيل الدخول الموحّد وموفّري خدمات المصادقة التابعين لجهات خارجية، ما يوفّر بيئة آمنة ومريحة تتيح للمستخدمين مزامنة بيانات الاعتماد وإدارتها. بالنسبة إلى المطوّرين الذين يستخدمون بيانات اعتماد FIDO2 محلية، عليهم تعديل تطبيقاتهم لتتيح مصادقة باستخدام مفتاح مرور من خلال الدمج مع Credential Manager API. يوضّح هذا المستند كيفية نقل مشروعك من FIDO2 إلى Credential Manager.

أسباب للانتقال من FIDO2 إلى "أداة إدارة بيانات الاعتماد"

في معظم الحالات، عليك نقل موفّر المصادقة في تطبيق Android إلى واجهة برمجة التطبيقات Credential Manager. تشمل أسباب الانتقال إلى "مدير بيانات الاعتماد" ما يلي:

  • إتاحة مفاتيح المرور: يتيح "مدير بيانات الاعتماد" استخدام مفاتيح المرور، وهي آلية مصادقة جديدة لا تتطلّب استخدام كلمات المرور، وتتسم بمستوى أمان أعلى وسهولة أكبر في الاستخدام مقارنةً بكلمات المرور.
  • طُرق متعددة لتسجيل الدخول: يتيح "مدير بيانات الاعتماد" استخدام طُرق متعددة لتسجيل الدخول، بما في ذلك كلمات المرور ومفاتيح المرور وطُرق تسجيل الدخول الموحّد. يسهّل ذلك على المستخدمين المصادقة على تطبيقك، بغض النظر عن طريقة المصادقة المفضّلة لديهم.
  • إتاحة استخدام موفّري بيانات اعتماد تابعين لجهات خارجية: على نظام التشغيل Android 14 والإصدارات الأحدث، يتيح "مدير بيانات الاعتماد" استخدام عدّة موفّري بيانات اعتماد تابعين لجهات خارجية. وهذا يعني أنّه يمكن للمستخدمين استخدام بيانات الاعتماد الحالية من مقدّمي خدمات آخرين لتسجيل الدخول إلى تطبيقك.
  • تجربة مستخدم متّسقة: يوفّر "مدير بيانات الاعتماد" تجربة مستخدم أكثر اتساقًا للمصادقة على مستوى التطبيقات وآليات تسجيل الدخول. يسهّل ذلك على المستخدمين فهم مسار المصادقة في تطبيقك واستخدامه.

لبدء عملية نقل البيانات من FIDO2 إلى Credential Manager، اتّبِع الخطوات التالية.

تعديل العناصر التابعة

  1. عدِّل المكوِّن الإضافي Kotlin في ملف build.gradle الخاص بمشروعك إلى الإصدار 1.8.10 أو إصدار أحدث.

      plugins {
        //…
          id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
        //…
      }
    
  2. في build.gradle الخاص بمشروعك، عدِّل التبعيات لاستخدام أحدث إصدارات مكتبتَي Credential Manager وPlay Services 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 below:
        implementation 'androidx.credentials:credentials-play-services-auth:<latest-version>'
        // ...
      }
    
  3. استبدِل عملية تهيئة FIDO بعملية تهيئة Credential Manager. أضِف بيان الإذن هذا إلى الفئة التي تستخدمها لإنشاء مفتاح المرور وطرق تسجيل الدخول:

    val credMan = CredentialManager.create(context)
    

إنشاء مفاتيح مرور

عليك إنشاء مفتاح مرور جديد وربطه بحساب مستخدم وتخزين المفتاح العام لمفتاح المرور على الخادم قبل أن يتمكّن المستخدم من تسجيل الدخول باستخدام مفتاح المرور. يمكنك إعداد تطبيقك بهذه الإمكانية من خلال تعديل طلبات وظيفة التسجيل.

الشكل 1. يوضّح هذا الشكل كيفية تبادل البيانات بين التطبيق والخادم عند إنشاء مفتاح مرور باستخدام &quot;مدير بيانات الاعتماد&quot;.
  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. أزِل بأمان أي طرق تعالج طلبات تشغيل الأنشطة ونتائج الأنشطة من طريقة العرض.

  4. بما أنّ registerRequest() تعرض الآن JSONObject، ليس عليك إنشاء PendingIntent. استبدِل الغرض الذي تم عرضه بـ JSONObject. عدِّل طلبات تشغيل الأهداف لاستدعاء createCredential() من واجهة برمجة التطبيقات Credential Manager 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. أزِل بأمان أي طرق تعالج مشغّل الأهداف ونتائج الأنشطة من طريقة العرض.

  4. بما أنّ signInRequest() تعرض الآن JSONObject، لن تحتاج إلى إنشاء PendingIntent. استبدِل النية التي تم إرجاعها بـ JSONObject، واستدعِ getCredential() من طرق واجهة برمجة التطبيقات.

    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. بعد نجاح المكالمة، أرسِل الردّ إلى الخادم للتحقّق من صحة بيانات المستخدم والمصادقة عليها. تتشابه مَعلمات الطلب والاستجابة الخاصة بطلب البيانات من واجهة برمجة التطبيقات هذا مع عملية تنفيذ FIDO2، لذا لا يلزم إجراء أي تغييرات.

مراجع إضافية