تنفيذ ميزة "تأكيد رقم الهاتف"

يوضّح هذا الدليل بالتفصيل كيفية استخدام DigitalCredential API للحصول على أرقام هواتف تم التحقّق منها للمستخدمين. تتضمّن العملية خطوتَين:

  1. التحقّق المُسبق: يمكنك التحقّق مما إذا كان مشغّل شبكة الجوّال الخاص بالمستخدم يتيح استخدام واجهة برمجة التطبيقات هذه من خلال طلب بيانات من واجهة برمجة التطبيقات Android Telephony API. إذا كان مشغّل شبكة الجوّال غير متوافق، يمكنك الرجوع إلى مسار الرسائل القصيرة العادي.
  2. طلب TS.43 token: يطلب تطبيق العميل ("أداة التحقّق") رمزًا مميزًا مؤقتًا TS.43 من جهاز المستخدم. ‫TS.43 token هو بيانات اعتماد صادرة عن مشغّل شبكة الجوّال تمثّل هوية المستخدم.
  3. استبدال الرمز المميز برقم هاتف: يستبدل الواجهة الخلفية لتطبيقك TS.43 token بمجمّع أو مشغّل شبكة الجوّال برقم الهاتف الذي تم التحقق منه للمستخدم.

التوافق مع إصدارات Android

تتوفّر واجهة برمجة التطبيقات لتأكيد رقم الهاتف على نظام التشغيل Android 10 (المستوى 29 من واجهة برمجة التطبيقات) والإصدارات الأحدث.

المتطلبات الأساسية

لتنفيذ عملية تأكيد رقم الهاتف باستخدام DigitalCredential API، يجب أن يكون لديك حساب لدى مجمِّع أو مشغّل شبكة جوّال. يتفاعل المجمِّع مع مشغّلي شبكات الجوّال ويوفّر واجهة برمجة التطبيقات اللازمة لتطبيقك، وعادةً ما تكون نقطة نهاية لواجهة برمجة تطبيقات سحابية قابلة للفوترة. في هذا الدليل، يُشار إلى كل من المجمّعين وشركات النقل بشكل جماعي باسم المجمّعين.

عليك أيضًا إضافة التبعيات التالية إلى نص Gradle البرمجي للإصدار:

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"
}

التنفيذ

قبل طلب Digital Credentials API، تحقَّق مما إذا كان مشغّل شبكة الجوّال الخاص بالمستخدم يتيح هذه الميزة باستخدام Android Telephony API.

أضِف الإذن READ_PHONE_STATE إلى ملف AndroidManifest.xml.

val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val carrierId = telephonyManager.getSimCarrierId()

باستخدام رقم التعريف هذا، يمكنك التأكّد من توافق TS.43 مع الخلفية الخاصة بمجمّع البيانات. إذا كان مشغّل شبكة الجوّال لهذا المستخدم غير متوافق، يمكنك الرجوع إلى طريقة بديلة مثل الرسائل القصيرة، لأنّ استدعاء Digital Credentials API يعرض نتيجة فارغة. بخلاف ذلك، يمكنك المتابعة باستخدام مسار "بيانات الاعتماد الرقمية".

تتّبع العملية الشاملة بشكل عام الخطوات التالية:

  1. طلب مَعلمات DCQL (لغة طلب بيانات الاعتماد الرقمية) من مجمِّع: يمكنك استدعاء مجمِّع واحد أو أكثر وطلب مجموعة من مَعلمات DCQL. تتيح لك لغة DCQL تحديد بيانات الاعتماد الرقمية التي تحتاج إليها من كل مجمِّع.
  2. إنشاء طلب OpenID4VP: من الخلفية في تطبيقك، أنشئ طلب OpenID4VP مع تضمين مَعلمات DCQL من المجمِّع. بعد ذلك، أرسِل طلب OpenID4VP إلى تطبيق العميل.

  3. استدعاء واجهة برمجة التطبيقات Credential Manager API: في تطبيق العميل، استخدِم واجهة برمجة التطبيقات Credential Manager API لإرسال طلب OpenID4VP إلى نظام التشغيل. في المقابل، ستتلقّى عنصر استجابة OpenID4VP يحتوي على TS.43 Digital Credential. يتم تشفير بيانات الاعتماد هذه ولا يمكن فك تشفيرها إلا من خلال المجمّع المرتبط بها. بعد تلقّي الرمز المميّز لمشغّل شبكة الجوّال، أرسِل الردّ من تطبيق العميل إلى الخلفية الخاصة بالتطبيق.

  4. التحقّق من صحة الردّ: في الخلفية الخاصة بتطبيقك، تحقَّق من صحة ردّ OpenID4VP.

  5. التبادل للحصول على رقم الهاتف: من الواجهة الخلفية لتطبيقك، أرسِل TS.43 Digital Credential إلى المجمِّع. يتحقّق المجمِّع من بيانات الاعتماد ويعرض رقم الهاتف الذي تم التحقّق منه.

صورة تعرض خطوات طلب تأكيد رقم الهاتف
الشكل 1:دورة حياة طلب تأكيد رقم الهاتف، بدءًا من طلب الواجهة الخلفية لخدمة "التحقّق" من المجمِّع توفير المَعلمات وانتهاءً بإرجاع رقم هاتف تم تأكيده

طلب مَعلمات DCQL من مجمِّع

من الواجهة الخلفية لتطبيقك، أرسِل طلبًا إلى المجمِّع للحصول على عنصر بيانات اعتماد بلغة طلب البحث عن بيانات الاعتماد الرقمية (DCQL). احرص على تقديم رقم خاص ومعرّف طلب في طلبك. يعرض المجمّع كائن بيانات اعتماد DCQL، والذي يتضمّن بنية مشابهة لما يلي:

{
  // The credential ID is mapped to the request ID that is sent in your request to the aggregator.
  "id": "aggregator1",
  "format": "dc-authorization+sd-jwt",
  "meta": {
    "vct_values": [
      "number-verification/device-phone-number/ts43"
    ],
    "credential_authorization_jwt": "..."
  },
  "claims": [
    {
      "path": ["subscription_hint"],
      "values": [1]
    },
    {
      "path": ["phone_number_hint"],
      "values": ["+14155552671"]
    }
  ]
}

إنشاء طلب OpenID4VP

أولاً، من الواجهة الخلفية لتطبيقك، أنشئ عنصر dcql_query من خلال وضع عنصر بيانات الاعتماد DCQL في مصفوفة credentials مدمجة داخل عنصر dcql_query كما هو موضّح في المثال التالي:

"dcql_query": {
  "credentials": [
    {
      "id": "aggregator1",
      "format": "dc-authorization+sd-jwt",
      "meta": {
        "vct_values": [
          "number-verification/device-phone-number/ts43"
        ],
        "credential_authorization_jwt": "..."
      },
      "claims": [
        {
          "path": ["subscription_hint"],
          "values": [1]
        },
        {
          "path": ["phone_number_hint"],
          "values": ["+14155552671"]
        }
      ]
    }
  ]
}

بعد ذلك، أنشئ طلب OpenID4VP بالبنية التالية:

{
  "protocol": "openid4vp-v1-unsigned",
  "data": {
    "response_type": "vp_token",
    "response_mode": "dc_api",
    "nonce": "...",
    "dcql_query": { ... }
  }
}
  • ‫protocol: يجب ضبط هذه السمة على openid4vp-v1-unsigned لطلبات تأكيد رقم الهاتف.
  • ‫response_type وresponse_mode: ثوابت تشير إلى تنسيق الطلب مع القيم الثابتة vp_token وdc_api على التوالي.
  • ‫nonce: قيمة فريدة تنشئها الواجهة الخلفية لكل طلب. يجب أن يتطابق رقم nonce في عنصر بيانات اعتماد DCQL الخاص بالمجمّع مع رقم nonce هذا.
  • ‫dcql_query: في هذه الحالة، استخدِم السمة dcql_query للإشارة إلى أنّه يتم طلب TS.43 Digital Credential. يمكنك أيضًا طلب بيانات اعتماد رقمية أخرى هنا.

بعد ذلك، عليك تضمين طلب OpenID4VP في عنصر طلب بيانات من واجهة برمجة التطبيقات DigitalCredential وإرساله إلى تطبيق العميل.

{
  "requests":
    [
      {
        "protocol": "openid4vp-v1-unsigned",
        "data": {
          "response_type": "vp_token",
          "response_mode": "dc_api",
          "nonce": "...",
          "dcql_query": { ... }
        }
      }
    ]
}

يوضّح المقتطف التالي كيفية إنشاء طلب بيانات من واجهة برمجة التطبيقات DigitalCredential:

def GenerateDCRequest():
    credentials = []
    aggregator1_dcql = call_aggregator_endpoint(nonce, "aggregator1", additional_params)
    credentials.append(aggregator1_dcql) # You can optionally work with multiple
    # aggregators, or request other types of credentials

    val dc_request =
    {
      "requests":
        [
          {
            "protocol": "openid4vp-v1-unsigned",
            "data": {
              "response_type": "vp_token",
              "response_mode": "dc_api",
              "nonce": "...",
              "dcql_query": {"credentials": credentials}
            }
          }
        ]
    }
    return dc_request

استدعاء واجهة برمجة التطبيقات Credential Manager API

في تطبيق العميل، أرسِل طلب بيانات من واجهة برمجة التطبيقات إلى واجهة Credential Manager API، مع تضمين طلب DigitalCredential API الذي توفّره الخلفية في تطبيقك.

val requestJson = generateTs43DigitalCredentialRequestFromServer()
val digiCredOption = GetDigitalCredentialOption(requestJson = requestJson)
val getCredRequest = GetCredentialRequest(
    listOf(digiCredOption)
)

coroutineScope.launch {
  try {
    val response = credentialManager.getCredential(
      context = activityContext,
      request = getCredRequest
    )
    val credential = response.credential
    when (credential) {
      is DigitalCredential -> {
        val responseJson = credential.credentialJson
        validateResponseOnServer(responseJson)
      }
      else -> {
        // Catch any unrecognized credential type here.
        Log.e(TAG, "Unexpected type of credential ${credential.type}")
      }
    }
  } catch (e : GetCredentialException) {
      // If user cancels the operation, the feature isn't available, or the
      // SIM doesn't support the feature, a GetCredentialCancellationException
      // will be returned. Otherwise, a GetCredentialUnsupportedException will
      // be returned with details in the exception message.
      handleFailure(e)
  }
}

يحتوي الردّ من واجهة برمجة التطبيقات DigitalCredential على الردّ من OpenID4VP. في ما يلي ملف JSON نموذجي لبيانات الاعتماد من نتيجة DigitalCredential:

{
  "protocol": "openid4vp-v1-unsigned",

  "data": {
    "vp_token": {
      "aggregator1": ["eyJhbGciOiAiRVMy..."] # The encrypted TS.43 Digital
                                             # Credential in an array structure.
    }
  }
}

من تطبيق العميل، أرسِل الردّ من واجهة برمجة التطبيقات DigitalCredential API إلى خادم الخلفية حيث يمكن التحقّق من صحته واستخدامه لتبادل رقم الهاتف الذي تم التحقّق منه مع مجمّع.

في بعض الحالات، قد يحتوي الردّ على خطأ TS.43. يكون الردّ على الخطأ عبارة عن عنصر JSON يتّبع تنسيق الردّ على الخطأ في OpenID4VP:

{
  "protocol": "openid4vp-v1-unsigned",

  "data": {
    "error": "<error_code>",
    "error_description": "<Human-readable description of the error>",
  }
}

هناك قيمتان محتملتان لـ error_code:

  • invalid_request: يشير ذلك إلى أنّ الطلب غير صالح.
  • ‫server_error: يشير ذلك إلى حدوث أخطاء أثناء معالجة الطلب. قد يكون ذلك بسبب أخطاء محلية أو مشاكل في TS.43.

يوفّر الحقل error_description تفاصيل إضافية حول المشكلة.

التحقّق من صحة استجابة "بيانات الاعتماد الرقمية"

في ما يلي مثال على كيفية تحليل الاستجابة وتنفيذ خطوة التحقّق في الخلفية لتطبيقك:

def processDigitalCredentialsResponse(response):
  # Step 1: Parse out the TS.43 Digital Credential from the response
  openId4VpResponse = response['data']

  ts43_digital_credential = openId4VpResponse['vp_token']["aggregator1"][0]

  # Step 2: Perform response validation
  verifyResponse(ts43_digital_credential)

def verifyResponse(ts43_digital_credential):
  # The returned ts43_digital_credential is an SD-JWT-based Verifiable Credentials
  # (SD-JWT VC) as defined in this IETF spec. The section 3.4 of the specification
  # outlines how to validate the credential. At a high level, the steps involves
  # validating (1) the nonce in the response credential matches the one in the
  # request, (2) the integrity of the credential by checking the credential is
  # signed by the trusted issuer Android Telephony, and (3) other validity
  # properties associated with this credential, such as issue time and expiration
  # time

  # In most cases, you can use an SD-JWT VC library to perform these validations.

  # Some aggregators may also perform the validation logic for you. Check with your
  # aggregator to decide the exact scope of the validation required.

التبديل برقم هاتف

من الواجهة الخلفية لتطبيقك، أرسِل TS.43 Digital Credential الذي تم التحقّق منه إلى نقطة نهاية المجمِّع للتحقّق من بيانات الاعتماد وتلقّي رقم الهاتف الذي تم التحقّق منه.

def processDigitalCredentialsResponse(response):
  # ... prior steps

  # Step 3: Call aggregator endpoint to exchange the verified phone number
  callAggregatorPnvEndpoint(ts43_digital_credential)

اختياري: إضافة ميزة التحقّق من المستخدم

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

"claims": [
  //... other claims
  {
    "path": [
      "user_verification_hint" // Use this to enable biometric authentication
    ]
  }
]

سيحتوي ردّ OpenID4VP بعد ذلك على سمة user_verification_hint في نص الحمولة. هناك 3 قيم محتملة:

  • ‫success: إذا نجحت عملية التحقّق من المستخدم
  • ‫absent: إذا تعذّر التحقّق من المستخدم، مثلاً في حال عدم إعداد عامل المعرفة لشاشة القفل
  • ‫error: إذا تعذّر التحقّق من المستخدم

إذا ألغى المستخدم عملية التحقّق، سيتم عرض الخطأ GetCredentialCancellationException.