از FIDO2 به Credential Manager مهاجرت کنید

با پشتیبانی از کلیدهای عبور ، ورود به سیستم فدرال، و ارائه دهندگان احراز هویت شخص ثالث، Credential Manager API توصیه شده برای احراز هویت در Android است که یک محیط امن و راحت را فراهم می کند که به کاربران امکان می دهد اعتبارنامه های خود را همگام و مدیریت کنند. برای توسعه‌دهندگانی که از اعتبارنامه‌های محلی FIDO2 استفاده می‌کنند، باید برنامه خود را برای پشتیبانی از احراز هویت رمز عبور با ادغام با Credential Manager API به‌روزرسانی کنید. این سند نحوه انتقال پروژه خود از FIDO2 به Credential Manager را شرح می دهد.

دلایل مهاجرت از FIDO2 به Credential Manager

در بیشتر موارد، باید ارائه‌دهنده احراز هویت برنامه Android خود را به Credential Manager منتقل کنید. دلایل مهاجرت به Credential Manager عبارتند از:

  • پشتیبانی از رمز عبور: Credential Manager از کلیدهای عبور پشتیبانی می کند، یک مکانیسم جدید احراز هویت بدون رمز عبور که امن تر و استفاده از آن آسان تر از رمزهای عبور است.
  • روش‌های ورود چندگانه: Credential Manager از روش‌های ورود به سیستم چندگانه، از جمله رمز عبور، کلیدهای عبور و روش‌های ورود به سیستم فدرال پشتیبانی می‌کند. این امر باعث می‌شود تا کاربران بدون در نظر گرفتن روش احراز هویت ترجیحی، احراز هویت در برنامه شما را آسان‌تر کنند.
  • پشتیبانی از ارائه‌دهنده اعتبار شخص ثالث: در Android 14 و بالاتر، Credential Manager از چندین ارائه‌دهنده اعتبار شخص ثالث پشتیبانی می‌کند. این بدان معناست که کاربران شما می توانند از اعتبارنامه های موجود خود از سایر ارائه دهندگان برای ورود به برنامه شما استفاده کنند.
  • تجربه کاربری ثابت: Credential Manager تجربه کاربری سازگارتری را برای احراز هویت در بین برنامه‌ها و مکانیسم‌های ورود به سیستم ارائه می‌کند. این امر درک و استفاده از جریان احراز هویت برنامه شما را برای کاربران آسان تر می کند.

برای شروع مهاجرت از 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. این شکل نشان می دهد که چگونه داده ها بین برنامه و سرور رد و بدل می شود که یک رمز عبور با استفاده از Credential Manager ایجاد می شود.
  1. برای به دست آوردن پارامترهای لازم که در حین ایجاد کلید عبور به متد createCredential() فرستاده می شوند، name("residentKey").value("required") همانطور که در مشخصات WebAuthn توضیح داده شده) به فراخوانی سرور registerRequest() خود اضافه کنید.

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .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(
            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. با خیال راحت هر روشی را که به تماس‌های راه‌انداز هدف و نتیجه فعالیت رسیدگی می‌کند، از دید خود حذف کنید.

  4. از آنجایی که registerRequest() اکنون یک JSONObject برمی گرداند، نیازی به ایجاد PendingIntent ندارید. هدف بازگشتی را با یک JSONObject جایگزین کنید. تماس‌های راه‌انداز قصد خود را برای فراخوانی createCredential() از Credential Manager API به‌روزرسانی کنید. متد API createCredential() را فراخوانی کنید.

    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. پس از موفقیت آمیز بودن تماس، پاسخ را به سرور ارسال کنید. درخواست و پاسخ برای این فراخوان مشابه اجرای FIDO2 است، بنابراین نیازی به تغییر نیست.

احراز هویت با کلیدهای عبور

پس از راه‌اندازی ایجاد رمز عبور، می‌توانید برنامه خود را طوری تنظیم کنید که به کاربران اجازه ورود به سیستم و احراز هویت با استفاده از کلیدهای عبور خود را بدهد. برای انجام این کار، کد احراز هویت خود را برای رسیدگی به نتایج Credential Manager به روز می کنید و تابعی را برای احراز هویت از طریق کلیدهای عبور پیاده سازی می کنید.

شکل 2. جریان احراز هویت رمز عبور مدیر اعتبار.
  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() را از متدهای API خود فراخوانی کنید.

    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. پس از موفقیت آمیز بودن تماس، پاسخ را به سرور ارسال کنید تا کاربر را تأیید و احراز هویت کند. پارامترهای درخواست و پاسخ برای این فراخوانی API مشابه اجرای FIDO2 است، بنابراین نیازی به تغییر نیست.

منابع اضافی

،

با پشتیبانی از کلیدهای عبور ، ورود به سیستم فدرال، و ارائه دهندگان احراز هویت شخص ثالث، Credential Manager API توصیه شده برای احراز هویت در Android است که یک محیط امن و راحت را فراهم می کند که به کاربران امکان می دهد اعتبارنامه های خود را همگام و مدیریت کنند. برای توسعه‌دهندگانی که از اعتبارنامه‌های محلی FIDO2 استفاده می‌کنند، باید برنامه خود را برای پشتیبانی از احراز هویت رمز عبور با ادغام با Credential Manager API به‌روزرسانی کنید. این سند نحوه انتقال پروژه خود از FIDO2 به Credential Manager را شرح می دهد.

دلایل مهاجرت از FIDO2 به Credential Manager

در بیشتر موارد، باید ارائه‌دهنده احراز هویت برنامه Android خود را به Credential Manager منتقل کنید. دلایل مهاجرت به Credential Manager عبارتند از:

  • پشتیبانی از رمز عبور: Credential Manager از کلیدهای عبور پشتیبانی می کند، یک مکانیسم جدید احراز هویت بدون رمز عبور که امن تر و استفاده از آن آسان تر از رمزهای عبور است.
  • روش‌های ورود چندگانه: Credential Manager از روش‌های ورود به سیستم چندگانه، از جمله رمز عبور، کلیدهای عبور و روش‌های ورود به سیستم فدرال پشتیبانی می‌کند. این امر باعث می‌شود تا کاربران بدون در نظر گرفتن روش احراز هویت ترجیحی، احراز هویت در برنامه شما را آسان‌تر کنند.
  • پشتیبانی از ارائه‌دهنده اعتبار شخص ثالث: در Android 14 و بالاتر، Credential Manager از چندین ارائه‌دهنده اعتبار شخص ثالث پشتیبانی می‌کند. این بدان معناست که کاربران شما می توانند از اعتبارنامه های موجود خود از سایر ارائه دهندگان برای ورود به برنامه شما استفاده کنند.
  • تجربه کاربری ثابت: Credential Manager تجربه کاربری سازگارتری را برای احراز هویت در بین برنامه‌ها و مکانیسم‌های ورود به سیستم ارائه می‌کند. این امر درک و استفاده از جریان احراز هویت برنامه شما را برای کاربران آسان تر می کند.

برای شروع مهاجرت از 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. این شکل نشان می دهد که چگونه داده ها بین برنامه و سرور رد و بدل می شود که یک رمز عبور با استفاده از Credential Manager ایجاد می شود.
  1. برای به دست آوردن پارامترهای لازم که در حین ایجاد کلید عبور به متد createCredential() فرستاده می شوند، name("residentKey").value("required") همانطور که در مشخصات WebAuthn توضیح داده شده) به فراخوانی سرور registerRequest() خود اضافه کنید.

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .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(
            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. با خیال راحت هر روشی را که به تماس‌های راه‌انداز هدف و نتیجه فعالیت رسیدگی می‌کند، از دید خود حذف کنید.

  4. از آنجایی که registerRequest() اکنون یک JSONObject برمی گرداند، نیازی به ایجاد PendingIntent ندارید. هدف بازگشتی را با یک JSONObject جایگزین کنید. تماس‌های راه‌انداز قصد خود را برای فراخوانی createCredential() از Credential Manager API به‌روزرسانی کنید. متد API createCredential() را فراخوانی کنید.

    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. پس از موفقیت آمیز بودن تماس، پاسخ را به سرور ارسال کنید. درخواست و پاسخ برای این فراخوان مشابه اجرای FIDO2 است، بنابراین نیازی به تغییر نیست.

احراز هویت با کلیدهای عبور

پس از راه‌اندازی ایجاد رمز عبور، می‌توانید برنامه خود را طوری تنظیم کنید که به کاربران اجازه ورود به سیستم و احراز هویت با استفاده از کلیدهای عبور خود را بدهد. برای انجام این کار، کد احراز هویت خود را برای رسیدگی به نتایج Credential Manager به روز می کنید و تابعی را برای احراز هویت از طریق کلیدهای عبور پیاده سازی می کنید.

شکل 2. جریان احراز هویت رمز عبور مدیر اعتبار.
  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() را از متدهای API خود فراخوانی کنید.

    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. پس از موفقیت آمیز بودن تماس، پاسخ را به سرور ارسال کنید تا کاربر را تأیید و احراز هویت کند. پارامترهای درخواست و پاسخ برای این فراخوانی API مشابه اجرای FIDO2 است، بنابراین نیازی به تغییر نیست.

منابع اضافی

،

با پشتیبانی از کلیدهای عبور ، ورود به سیستم فدرال، و ارائه دهندگان احراز هویت شخص ثالث، Credential Manager API توصیه شده برای احراز هویت در Android است که یک محیط امن و راحت را فراهم می کند که به کاربران امکان می دهد اعتبارنامه های خود را همگام و مدیریت کنند. برای توسعه‌دهندگانی که از اعتبارنامه‌های محلی FIDO2 استفاده می‌کنند، باید برنامه خود را برای پشتیبانی از احراز هویت رمز عبور با ادغام با Credential Manager API به‌روزرسانی کنید. این سند نحوه انتقال پروژه خود از FIDO2 به Credential Manager را شرح می دهد.

دلایل مهاجرت از FIDO2 به Credential Manager

در بیشتر موارد، باید ارائه‌دهنده احراز هویت برنامه Android خود را به Credential Manager منتقل کنید. دلایل مهاجرت به Credential Manager عبارتند از:

  • پشتیبانی از رمز عبور: Credential Manager از کلیدهای عبور پشتیبانی می کند، یک مکانیسم جدید احراز هویت بدون رمز عبور که امن تر و استفاده از آن آسان تر از رمزهای عبور است.
  • روش‌های ورود چندگانه: Credential Manager از روش‌های ورود به سیستم چندگانه، از جمله رمز عبور، کلیدهای عبور و روش‌های ورود به سیستم فدرال پشتیبانی می‌کند. این امر باعث می‌شود تا کاربران بدون در نظر گرفتن روش احراز هویت ترجیحی، احراز هویت در برنامه شما را آسان‌تر کنند.
  • پشتیبانی از ارائه‌دهنده اعتبار شخص ثالث: در Android 14 و بالاتر، Credential Manager از چندین ارائه‌دهنده اعتبار شخص ثالث پشتیبانی می‌کند. این بدان معناست که کاربران شما می توانند از اعتبارنامه های موجود خود از سایر ارائه دهندگان برای ورود به برنامه شما استفاده کنند.
  • تجربه کاربری ثابت: Credential Manager تجربه کاربری سازگارتری را برای احراز هویت در بین برنامه‌ها و مکانیسم‌های ورود به سیستم ارائه می‌کند. این امر درک و استفاده از جریان احراز هویت برنامه شما را برای کاربران آسان تر می کند.

برای شروع مهاجرت از 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. این شکل نشان می دهد که چگونه داده ها بین برنامه و سرور رد و بدل می شود که یک رمز عبور با استفاده از Credential Manager ایجاد می شود.
  1. برای به دست آوردن پارامترهای لازم که در حین ایجاد کلید عبور به متد createCredential() فرستاده می شوند، name("residentKey").value("required") همانطور که در مشخصات WebAuthn توضیح داده شده) به فراخوانی سرور registerRequest() خود اضافه کنید.

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .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(
            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. با خیال راحت هر روشی را که به تماس‌های راه‌انداز هدف و نتیجه فعالیت رسیدگی می‌کند، از دید خود حذف کنید.

  4. از آنجایی که registerRequest() اکنون یک JSONObject برمی گرداند، نیازی به ایجاد PendingIntent ندارید. هدف بازگشتی را با یک JSONObject جایگزین کنید. تماس‌های راه‌انداز قصد خود را برای فراخوانی createCredential() از Credential Manager API به‌روزرسانی کنید. متد API createCredential() را فراخوانی کنید.

    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. پس از موفقیت آمیز بودن تماس، پاسخ را به سرور ارسال کنید. درخواست و پاسخ برای این فراخوان مشابه اجرای FIDO2 است، بنابراین نیازی به تغییر نیست.

احراز هویت با کلیدهای عبور

پس از راه‌اندازی ایجاد رمز عبور، می‌توانید برنامه خود را طوری تنظیم کنید که به کاربران اجازه ورود به سیستم و احراز هویت با استفاده از کلیدهای عبور خود را بدهد. برای انجام این کار، کد احراز هویت خود را برای رسیدگی به نتایج Credential Manager به روز می کنید و تابعی را برای احراز هویت از طریق کلیدهای عبور پیاده سازی می کنید.

شکل 2. جریان احراز هویت رمز عبور مدیر اعتبار.
  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() را از متدهای API خود فراخوانی کنید.

    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. پس از موفقیت آمیز بودن تماس، پاسخ را به سرور ارسال کنید تا کاربر را تأیید و احراز هویت کند. پارامترهای درخواست و پاسخ برای این فراخوانی API مشابه اجرای FIDO2 است، بنابراین نیازی به تغییر نیست.

منابع اضافی

،

با پشتیبانی از کلیدهای عبور ، ورود به سیستم فدرال، و ارائه دهندگان احراز هویت شخص ثالث، Credential Manager API توصیه شده برای احراز هویت در Android است که یک محیط امن و راحت را فراهم می کند که به کاربران امکان می دهد اعتبارنامه های خود را همگام و مدیریت کنند. برای توسعه‌دهندگانی که از اعتبارنامه‌های محلی FIDO2 استفاده می‌کنند، باید برنامه خود را برای پشتیبانی از احراز هویت رمز عبور با ادغام با Credential Manager API به‌روزرسانی کنید. این سند نحوه انتقال پروژه خود از FIDO2 به Credential Manager را شرح می دهد.

دلایل مهاجرت از FIDO2 به Credential Manager

در بیشتر موارد، باید ارائه‌دهنده احراز هویت برنامه Android خود را به Credential Manager منتقل کنید. دلایل مهاجرت به Credential Manager عبارتند از:

  • پشتیبانی از رمز عبور: Credential Manager از کلیدهای عبور پشتیبانی می کند، یک مکانیسم جدید احراز هویت بدون رمز عبور که امن تر و استفاده از آن آسان تر از رمزهای عبور است.
  • روش‌های ورود چندگانه: Credential Manager از روش‌های ورود به سیستم چندگانه، از جمله رمز عبور، کلیدهای عبور و روش‌های ورود به سیستم فدرال پشتیبانی می‌کند. این امر باعث می‌شود تا کاربران بدون در نظر گرفتن روش احراز هویت ترجیحی، احراز هویت در برنامه شما را آسان‌تر کنند.
  • پشتیبانی از ارائه‌دهنده اعتبار شخص ثالث: در Android 14 و بالاتر، Credential Manager از چندین ارائه‌دهنده اعتبار شخص ثالث پشتیبانی می‌کند. این بدان معناست که کاربران شما می توانند از اعتبارنامه های موجود خود از سایر ارائه دهندگان برای ورود به برنامه شما استفاده کنند.
  • تجربه کاربری ثابت: Credential Manager تجربه کاربری سازگارتری را برای احراز هویت در بین برنامه‌ها و مکانیسم‌های ورود به سیستم ارائه می‌کند. این امر درک و استفاده از جریان احراز هویت برنامه شما را برای کاربران آسان تر می کند.

برای شروع مهاجرت از 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. این شکل نشان می دهد که چگونه داده ها بین برنامه و سرور رد و بدل می شود که یک رمز عبور با استفاده از Credential Manager ایجاد می شود.
  1. برای به دست آوردن پارامترهای لازم که در حین ایجاد کلید عبور به متد createCredential() فرستاده می شوند، name("residentKey").value("required") همانطور که در مشخصات WebAuthn توضیح داده شده) به فراخوانی سرور registerRequest() خود اضافه کنید.

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .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(
            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. با خیال راحت هر روشی را که به تماس‌های راه‌انداز هدف و نتیجه فعالیت رسیدگی می‌کند، از دید خود حذف کنید.

  4. از آنجایی که registerRequest() اکنون یک JSONObject برمی گرداند، نیازی به ایجاد PendingIntent ندارید. هدف بازگشتی را با یک JSONObject جایگزین کنید. تماس‌های راه‌انداز قصد خود را برای فراخوانی createCredential() از Credential Manager API به‌روزرسانی کنید. متد API createCredential() را فراخوانی کنید.

    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. پس از موفقیت آمیز بودن تماس، پاسخ را به سرور ارسال کنید. درخواست و پاسخ برای این فراخوان مشابه اجرای FIDO2 است، بنابراین نیازی به تغییر نیست.

احراز هویت با کلیدهای عبور

پس از راه‌اندازی ایجاد رمز عبور، می‌توانید برنامه خود را طوری تنظیم کنید که به کاربران اجازه ورود به سیستم و احراز هویت با استفاده از کلیدهای عبور خود را بدهد. برای انجام این کار، کد احراز هویت خود را برای رسیدگی به نتایج Credential Manager به روز می کنید و تابعی را برای احراز هویت از طریق کلیدهای عبور پیاده سازی می کنید.

شکل 2. جریان احراز هویت رمز عبور مدیر اعتبار.
  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() را از متدهای API خود فراخوانی کنید.

    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. پس از موفقیت آمیز بودن تماس، پاسخ را به سرور ارسال کنید تا کاربر را تأیید و احراز هویت کند. پارامترهای درخواست و پاسخ برای این فراخوانی API مشابه اجرای FIDO2 است، بنابراین نیازی به تغییر نیست.

منابع اضافی