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

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

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

Kotlin

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

Groovy

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 for Verifiable Presentations (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))

بعد ذلك، غلِّف 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": ""
}
 */

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

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

  • أصالة البيانات: يؤدي تأكيد عنوان URL للجهة المُصدرة (iss) وتوقيع SD-JWT إلى إثبات أنّ جهة موثوق بها أصدرت هذه البيانات.

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

  • تأكيد الجهة المُصدرة: تأكَّد من أنّ الحقل iss (الجهة المُصدرة) يطابق https://verifiablecredentials-pa.googleapis.com.
  • تأكيد التوقيع: تحقَّق من توقيع SD-JWT باستخدام المفاتيح العامة (JWKs) المتاحة على 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، على المطوّرين تنفيذ جسر JavaScript (JS Bridge) لتسهيل عملية النقل. يسمح هذا الجسر لعنصر WebView بإرسال إشارة إلى التطبيق الأصلي، الذي يمكنه بعد ذلك إجراء الاتصال الفعلي بـ Credential Manager API.