Как реализовать подтверждение номера телефона

В этом руководстве рассказывается, как использовать Digital Credentials API, чтобы получать подтвержденные номера телефонов пользователей. Процесс состоит из двух этапов:

  1. Предварительная проверка. Проверьте, поддерживает ли оператор пользователя этот API, отправив запрос к Android Telephony API. Если оператор не поддерживается, используйте обычный способ отправки SMS.
  2. Запрос TS.43 token. Клиентское приложение (верификатор) запрашивает временный токен TS.43 у устройства пользователя. TS.43 token – это удостоверение личности, выданное оператором связи.
  3. Обмен токена на номер телефона. Серверная часть приложения обменивает TS.43 token у агрегатора или оператора связи на подтвержденный номер телефона пользователя.

Совместимость с версиями Android

API подтверждения номера телефона поддерживается на устройствах с Android 10 (уровень API 29) и более поздними версиями.

Требования

Чтобы реализовать подтверждение номера телефона с помощью Digital Credentials API, вам нужен аккаунт у агрегатора или оператора мобильной связи. Агрегатор взаимодействует с операторами и предоставляет необходимый API для вашего приложения, обычно в виде платной конечной точки облачного API. В этом руководстве и агрегаторы, и операторы связи называются агрегаторами.

Вам также нужно добавить в скрипт сборки Gradle следующие зависимости:

Котлин

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

Круто

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

Реализация

Прежде чем вызывать 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 в серверной части агрегатора. Если оператор пользователя не поддерживается, вы можете использовать альтернативный метод, например SMS, поскольку вызов Digital Credentials API возвращает пустой результат. В противном случае продолжайте использовать Digital Credentials API.

Процесс обычно состоит из следующих этапов:

  1. Запросите параметры DCQL (Digital Credential Query Language) у агрегатора. Вызовите один или несколько агрегаторов и запросите набор параметров 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. Убедитесь, что в запросе указаны nonce и идентификатор запроса. Агрегатор возвращает объект учетных данных 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 – уникальное значение, которое генерируется вашим бэкендом для каждого запроса. Одноразовый код в объекте учетных данных DCQL агрегатора должен совпадать с этим кодом.
  • dcql_query. В этом случае используйте свойство dcql_query, чтобы указать, что запрашивается TS.43 Digital Credential. Здесь же можно запросить другие цифровые документы.

Затем упакуйте запрос OpenID4VP в объект запроса DigitalCredential API и отправьте его в клиентское приложение.

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

В следующем фрагменте кода показано, как создать запрос к DigitalCredential API:

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 API содержит ответ 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 = response['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)