Implementa la verificación del número de teléfono

En esta guía, se detalla cómo usar la API de DigitalCredential para obtener números de teléfono verificados para tus usuarios. El proceso implica dos pasos:

  1. Verificación previa: Consulta la API de Android Telephony para comprobar si el operador del usuario admite esta API. Si el operador no es compatible, recurre a tu flujo de SMS normal.
  2. Solicita un TS.43 token: Tu app cliente (el "verificador") solicita un token TS.43 temporal del dispositivo del usuario. El TS.43 token es una credencial emitida por el operador que representa la identidad del usuario.
  3. Intercambia el token por un número de teléfono: El backend de tu app intercambia el TS.43 token con un agregador o un operador por el número de teléfono verificado del usuario.

Compatibilidad con versiones de Android

La API de verificación del número de teléfono es compatible con Android 10 (nivel de API 29) y versiones posteriores.

Requisitos previos

Para implementar la verificación del número de teléfono con la API de DigitalCredential, necesitas una cuenta con un agregador o un operador de telefonía celular. Un agregador interactúa con los operadores y proporciona la plataforma de API necesaria para tu app, por lo general, como un extremo de API de Cloud facturable. En esta guía, tanto los agregadores como los operadores se denominan de forma colectiva agregadores.

También debes agregar las siguientes dependencias a tu secuencia de comandos de compilación de 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"
}

Implementación

Antes de llamar a la API de Digital Credentials, verifica si el operador del usuario admite esta función con la API de Android Telephony.

Agrega el permiso READ_PHONE_STATE al archivo AndroidManifest.xml.

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

Con este ID, verifica la compatibilidad con TS.43 con el backend de tu agregador. Si no se admite el operador de este usuario, puedes recurrir a un método alternativo, como SMS, ya que llamar a la API de Digital Credentials devuelve un resultado vacío. De lo contrario, continúa con el flujo de credenciales digitales.

Por lo general, el proceso de extremo a extremo sigue estos pasos:

  1. Solicita parámetros de DCQL (lenguaje de consulta de credenciales digitales) a un agregador: Llama a uno o más agregadores y solicita un conjunto de parámetros de DCQL. DCQL te permite especificar las credenciales digitales exactas que necesitas de cada agregador.
  2. Crea la solicitud de OpenID4VP: Desde el backend de tu app, crea la solicitud de OpenID4VP y, al mismo tiempo, incluye los parámetros de DCQL del agregador. Luego, envía la solicitud de OpenID4VP a tu app cliente.

  3. Llama a la API de Credential Manager: En tu app cliente, usa la API de Credential Manager para enviar la solicitud de OpenID4VP al sistema operativo. Como respuesta, recibes un objeto de respuesta de OpenID4VP que contiene la TS.43 Digital Credential. Esta credencial está encriptada y solo la puede desencriptar el agregador asociado. Después de recibir el token del operador, envía la respuesta de tu app cliente al backend de la app.

  4. Valida la respuesta: En el backend de tu app, valida la respuesta de OpenID4VP.

  5. Intercambio por el número de teléfono: Desde el backend de tu app, envía el TS.43 Digital Credential al agregador. El agregador valida la credencial y devuelve el número de teléfono verificado.

Imagen que muestra el flujo de una solicitud de verificación del número de teléfono
Figura 1.La vida de una solicitud de verificación de número de teléfono, que comienza con el backend del verificador que solicita parámetros a un agregador y termina con un número de teléfono verificado que se devuelve.

Solicita parámetros de DCQL a un agregador

Desde el backend de tu app, envía una solicitud al agregador para obtener un objeto de credencial de Digital Credential Query Language (DCQL). Asegúrate de proporcionar un nonce y un ID de solicitud en tu solicitud. El agregador devuelve el objeto de credencial de DCQL, que tiene una estructura similar a la siguiente:

{
  // 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"]
    }
  ]
}

Crea la solicitud de OpenID4VP

Primero, desde el backend de tu app, crea un objeto dcql_query colocando el objeto de credencial de DCQL en un array credentials anidado dentro de un objeto dcql_query, como se muestra en el siguiente ejemplo:

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

Luego, crea una solicitud de OpenID4VP con la siguiente estructura:

{
  "protocol": "openid4vp-v1-unsigned",
  "data": {
    "response_type": "vp_token",
    "response_mode": "dc_api",
    "nonce": "...",
    "dcql_query": { ... }
  }
}
  • protocol: Se debe configurar como openid4vp-v1-unsigned para las solicitudes de verificación del número de teléfono.
  • response_type y response_mode: Son constantes que indican el formato de la solicitud con los valores fijos vp_token y dc_api, respectivamente.
  • nonce: Es un valor único que genera tu backend para cada solicitud. El nonce en el objeto de credencial de DCQL del agregador debe coincidir con este nonce.
  • dcql_query: En este caso, usa dcql_query para especificar que se solicita un TS.43 Digital Credential. También puedes solicitar otras credenciales digitales aquí.

Luego, une la solicitud de OpenID4VP en un objeto de solicitud a la API de DigitalCredential y envíala a la app cliente.

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

En el siguiente fragmento, se muestra cómo generar la solicitud a la API de 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

Llama a la API de Credential Manager

En tu app cliente, realiza una llamada a la API de Credential Manager con la solicitud a la API de DigitalCredential que proporciona el backend de tu app.

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

La respuesta de la API de DigitalCredential contiene la respuesta de OpenID4VP. Un JSON de credenciales típico del resultado de DigitalCredential es el siguiente:

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

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

Desde tu app cliente, envía la respuesta de la API de DigitalCredential de vuelta al servidor de backend, donde se puede validar y usar para intercambiarla por el número de teléfono verificado con un agregador.

En algunos casos, la respuesta puede contener un error TS.43. La respuesta de error es un objeto JSON que sigue el formato de respuesta de error de OpenID4VP:

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

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

Existen 2 valores posibles para error_code:

  • invalid_request: Indica que la solicitud tiene un formato incorrecto.
  • server_error: Indica que se produjeron errores cuando se procesó la solicitud. Podrían ser errores locales o problemas de TS.43.

El campo error_description proporciona detalles adicionales sobre el problema.

Valida la respuesta de la credencial digital

A continuación, se muestra un ejemplo de cómo analizar la respuesta y realizar el paso de validación en el backend de tu app:

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.

Cambiar por número de teléfono

Desde el backend de tu app, envía el TS.43 Digital Credential validado al extremo del agregador para validar la credencial y recibir el número de teléfono verificado.

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

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