تنفيذ عملية إثبات ملكية عنوان البريد الإلكتروني باستخدام Digital Credentials API

يوضّح هذا الدليل كيفية تنفيذ عملية استرداد عنوان البريد الإلكتروني الذي تم التحقّق منه باستخدام Digital Credentials Verifier API من خلال طلب OpenID for Verifiable Presentations (OpenID4VP).

إضافة التبعيات

في ملف build.gradle الخاص بتطبيقك، أضِف الاعتمادات التالية الخاصة بـ Credential Manager:

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha01")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha01")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha01"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha01"
}

تهيئة Credential Manager

استخدِم سياق تطبيقك أو نشاطك لإنشاء عنصر CredentialManager.

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

إنشاء طلب بيانات الاعتماد الرقمية

لطلب عنوان بريد إلكتروني تم تأكيده، أنشئ GetCredentialRequest يتضمّن GetDigitalCredentialOption. يتطلّب هذا الخيار requestJson سلسلة منسَّقة كطلب OpenID للعروض التقديمية القابلة للتحقّق (OpenID4VP).

يجب أن يتّبع طلب OpenID4VP JSON بنية محدّدة. تتيح الجهات الحالية بنية JSON مع غلاف "digital": {"requests": [...]} خارجي.

        val nonce = generateSecureRandomNonce()

// This request follows the OpenID4VP spec
        val openId4vpRequest = """
    {
      "requests": [
        {
          "protocol": "openid4vp-v1-unsigned",
          "data": {
            "response_type": "vp_token",
            "response_mode": "dc_api",
            "nonce": "$nonce",
            "dcql_query": {
              "credentials": [
                {
                  "id": "user_info_query",
                  "format": "dc+sd-jwt",
                   "meta": { 
                      "vct_values": ["UserInfoCredential"] 
                   },
                  "claims": [ 
                    {"path": ["email"]}, 
                    {"path": ["name"]},  
                    {"path": ["given_name"]},
                    {"path": ["family_name"]},
                    {"path": ["picture"]},
                    {"path": ["hd"]},
                    {"path": ["email_verified"]}
                  ]
                }
              ]
            }
          }
        }
      ]
    }
    """

        val getDigitalCredentialOption = GetDigitalCredentialOption(requestJson = openId4vpRequest)
        val request = GetCredentialRequest(listOf(getDigitalCredentialOption))

يتضمّن الطلب المعلومات الأساسية التالية:

  • طلب DCQL: يحدّد dcql_query نوع بيانات الاعتماد والمطالبات المطلوب الحصول عليها (email_verified). يمكنك طلب مطالبات أخرى لتحديد مستوى التحقّق. في ما يلي بعض المطالبات المحتملة:

    • email_verified: في الردّ، تكون هذه القيمة منطقية تشير إلى ما إذا تم تأكيد عنوان البريد الإلكتروني.
    • hd (النطاق المستضاف): يكون هذا الحقل فارغًا في الردّ.
  • إذا كان عنوان البريد الإلكتروني لا ينتهي بـ ‎@gmail.com، تحقّقت Google من هذا العنوان عند إنشاء حساب Google، ولكن لا يوجد تأكيد على أنّه لا يزال صالحًا. لذلك، بالنسبة إلى عناوين البريد الإلكتروني غير التابعة لـ Google، ننصحك بإضافة خطوة إضافية للتحقّق من هوية المستخدم، مثل إدخال رمز OTP. لفهم مخطط بيانات بيانات الاعتماد والقواعد المحدّدة للتحقّق من صحة الحقول، مثل email_verified، يُرجى الرجوع إلى أدلة Google Identity.

  • nonce: يتم إنشاء قيمة عشوائية فريدة وآمنة بطريقة مشفَّرة لكل طلب. وهذا أمر بالغ الأهمية للأمان، لأنّه يمنع هجمات إعادة الإرسال.

  • UserInfoCredential: تشير هذه القيمة إلى نوع معيّن من الشهادات الرقمية التي تتضمّن سمات المستخدم. يُعدّ تضمين ذلك في الطلب أمرًا أساسيًا للتمييز بين حالة استخدام تأكيد عنوان البريد الإلكتروني.

بعد ذلك، ضع openId4vpRequest JSON في GetDigitalCredentialOption، وأنشئ GetCredentialRequest، ثم استدعِ getCredential().

عرض الطلب للمستخدم

اعرض الطلب للمستخدم باستخدام واجهة المستخدم المضمّنة في "مدير الاعتماد".

try {
    // Requesting Digital Credential from user...
    val result = credentialManager.getCredential(activity, request)

    when (val credential = result.credential) {
        is DigitalCredential -> {
            val responseJsonString = credential.credentialJson

            // Successfully received digital credential response.

            // Next, parse this response and send it to your server.
            // ...
        }

        else -> {
            // handle Unexpected State() - Up to the developer
        }
    }
} catch (e: Exception) {
    // handle exceptions - Up to the developer
}

تحليل الردّ على العميل

بعد تلقّي الردّ، يمكنك إجراء تحليل أولي على العميل. ويكون ذلك مفيدًا لتعديل واجهة المستخدم على الفور، مثلاً من خلال عرض اسم المستخدم.

يستخرج الرمز التالي رمز JWT الخاص بالإفصاح الانتقائي الأولي ويستخدم أداة مساعدة لفك ترميز المطالبات.

// 1. Parse the outer JSON wrapper to get the `vp_token`
val responseData = JSONObject(responseJsonString)
val vpToken = responseData.getJSONObject("vp_token")

// 2. Extract the raw SD-JWT string
val credentialId = vpToken.keys().next()
val rawSdJwt = vpToken.getJSONArray(credentialId).getString(0)

// 3. Use your parser to get the verified claims
// Server-side validation/parsing is highly recommended.

// Assumes a local parser like the one in our SdJwtParser.kt sample
val claims = SdJwtParser.parse(rawSdJwt)
Log.d("TAG", "Parsed Claims: ${claims.toString(2)}")

// 4. Create your VerifiedUserInfo object with REAL data
val userInfo = VerifiedUserInfo(
    email = claims.getString("email"),
    displayName = claims.optString("name", claims.getString("email"))
)

التعامل مع الردّ

ستعرض واجهة برمجة التطبيقات Credential Manager الردّ DigitalCredential.

في ما يلي مثال على شكل responseJsonString الأولي، وشكل المطالبات بعد تحليل SD-JWT الداخلي الذي تحصل فيه على بيانات وصفية إضافية بالإضافة إلى عنوان البريد الإلكتروني الذي تم التحقّق منه:

/*
// Example of the raw JSON response from credential.credentialJson:
{
  "vp_token": {
    // This key matches the 'id' you set in your dcql_query
    "user_info_query": [
      // The SD-JWT string (Issuer JWT ~ Disclosures ~ Key Binding JWT)
      "eyJhbGciOiJ...~WyI...IiwgImVtYWlsIiwgInVzZXJAZXhhbXBsZS5jb20iXQ~...~eyJhbGciOiJ..."
    ]
  }
}

// Example of the parsed and verified claims from the SD-JWT on your server:
{
  "cnf": {
    "jwk": {..}
  },
  "exp": 1775688222,
  "iat": 1775083422,
  "iss": "https://verifiablecredentials-pa.googleapis.com",
  "vct": "UserInfoCredential",
  "email": "jane.doe.246745@gmail.com",
  "email_verified": true,
  "given_name": "Jane",
  "family_name": "Doe",
  "name": "Jane Doe",
  "picture": "http://example.com/janedoe/me.jpg",
  "hd": ""
}
 */

التحقّق على صعيد الخادم من إنشاء الحساب

بما أنّه يتم إثبات صحة عنوان البريد الإلكتروني الذي تم استرداده باستخدام التشفير، يمكنك تخطّي خطوة إثبات صحة عنوان البريد الإلكتروني باستخدام كلمة المرور صالحة لمرة واحدة (OTP)، ما يقلّل بشكل كبير من المشاكل التي تواجه المستخدمين عند الاشتراك ويزيد من الإحالات الناجحة. من الأفضل إجراء هذه العملية على الخادم. يرسل البرنامج الرد الأولي (الذي يتضمّن vp_token) ورمز الاستخدام لمرة واحدة الأصلي إلى نقطة نهاية خادم جديدة.

لإثبات الملكية، يجب أن يرسل تطبيقك responseJsonString الكامل إلى خادمك لإجراء عملية التحقّق من التشفير قبل إنشاء حساب أو تسجيل دخول المستخدم.

توفّر بيانات الاعتماد الرقمية مستويَين مهمَّين من التحقّق لخادمك، وهما:

  • صحة البيانات: يثبت التحقّق من عنوان URL الخاص بالجهة المصدرة (iss) وتوقيع SD-JWT أنّ جهة موثوقة أصدرت هذه البيانات.
  • هوية مقدّم المستند: يؤكّد التحقّق من الحقل cnf وتوقيع ربط المفتاح (kb) أنّ المستند تتم مشاركته من الجهاز نفسه الذي تم إصداره عليه في الأصل، ما يمنع اعتراضه أو استخدامه على جهاز آخر.

يجب أن يحقّق التحقّق من الصحة على الخادم ما يلي:

  • التحقّق من الجهة المصدرة: تأكَّد من أنّ حقل iss (الجهة المصدرة) يتطابق مع https://verifiablecredentials-pa.googleapis.com.
  • التحقّق من صحة التوقيع: تحقَّق من توقيع SD-JWT باستخدام المفاتيح العامة (JWK) المتاحة على https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.

للحصول على أمان كامل، احرص على التحقّق من صحة nonce أيضًا لمنع هجمات إعادة الإرسال.

من خلال الجمع بين هذه الخطوات، يمكن لخادمك التحقّق من صحة البيانات وهوية مقدّمها، ما يضمن عدم اعتراض بيانات الاعتماد أو تزويرها قبل توفير الحساب الجديد.

try {
    // Send the raw credential response and the original nonce to your server.
    // Your server must validate the response. createAccountWithVerifiedCredentials
    // is a custom implementation per each RP for server side verification and account creation.
    val serverResponse = createAccountWithVerifiedCredentials(responseJsonString, nonce)

    // Server returns the new account info (e.g., email, name)
    val claims = JSONObject(serverResponse.json)

    val userInfo = VerifiedUserInfo(
        email = claims.getString("email"),
        displayName = claims.optString("name", claims.getString("email"))
    )

    // handle response - Up to the developer
} catch (e: Exception) {
    // handle exceptions - Up to the developer
}

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

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

التوافق مع WebView

لكي يعمل المسار على WebView، على المطوّرين تنفيذ جسر JavaScript (JS Bridge) لتسهيل عملية التسليم. تتيح هذه الواجهة لـ Webview إرسال إشارة إلى التطبيق الأصلي، ما يتيح له بعد ذلك إجراء طلب البيانات الفعلي إلى Credential Manager API.

انظر أيضًا