تنفيذ عملية تأكيد عنوان البريد الإلكتروني

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

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

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

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

بدء استخدام 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).

يجب أن يتّبع طلب JSON الخاص بـ OpenID4VP بنية محدّدة. يتيح مقدّمو الخدمات الحاليون استخدام بنية 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 (موقع مستضاف): في الردّ، يكون هذا الحقل فارغًا.
  • ‫nonce: يتم إنشاء قيمة عشوائية فريدة وآمنة من الناحية التشفيرية لكل طلب. وهذا أمر بالغ الأهمية للأمان، لأنه يمنع هجمات إعادة التشغيل.
  • ‫UserInfoCredential: تشير هذه القيمة إلى نوع معيّن من بيانات الاعتماد الرقمية التي تحتوي على سمات المستخدم. يُعدّ تضمين هذه المعلومات في الطلب أمرًا بالغ الأهمية للتمييز بين حالات استخدام تأكيد عنوان البريد الإلكتروني.

بعد ذلك، عليك تضمين ملف JSON الخاص بـ openId4vpRequest في GetDigitalCredentialOption، وإنشاء GetCredentialRequest، واستدعاء getCredential().

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

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

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 للإفصاح الانتقائي (SD-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 API الاستجابة 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 باستخدام المفاتيح العامة (JWKs) المتوفّرة على https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.

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

By combining these steps, your server can validate both the authenticity of the data and the identity of the presenter, ensuring the credential wasn't intercepted or spoofed before provisioning the new account.

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 bridge (JS Bridge) لتسهيل عملية النقل. يتيح هذا الجسر لكائن WebView إرسال إشارة إلى التطبيق الأصلي، والذي يمكنه بعد ذلك إجراء الطلب الفعلي إلى Credential Manager API.

انظر أيضًا