Implémenter la vérification du numéro de téléphone

Ce guide explique comment utiliser l'API DigitalCredential pour obtenir des numéros de téléphone validés pour vos utilisateurs. Le processus se déroule en deux étapes :

  1. Vérification préalable : vérifiez si l'opérateur de l'utilisateur est compatible avec cette API en interrogeant l'API Android Telephony. Si l'opérateur n'est pas compatible, revenez à votre flux de SMS normal.
  2. Demander un TS.43 token : votre application cliente (le "vérificateur") demande un jeton TS.43 temporaire à l'appareil de l'utilisateur. Le TS.43 token est un identifiant émis par l'opérateur qui représente l'identité de l'utilisateur.
  3. Échangez le jeton contre un numéro de téléphone : le backend de votre application échange le TS.43 token avec un agrégateur ou un opérateur pour obtenir le numéro de téléphone validé de l'utilisateur.

Compatibilité avec les versions d'Android

L'API de vérification du numéro de téléphone est compatible avec Android 10 (niveau d'API 29) et versions ultérieures.

Prérequis

Pour implémenter la validation du numéro de téléphone avec l'API Digital Credentials, vous devez disposer d'un compte auprès d'un agrégateur ou d'un opérateur mobile. Un agrégateur interagit avec les opérateurs et fournit la surface d'API nécessaire à votre application, généralement en tant que point de terminaison d'API cloud facturable. Dans ce guide, les agrégateurs et les opérateurs sont collectivement appelés agrégateurs.

Vous devez également ajouter les dépendances suivantes à votre script de compilation 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"
}

Implémentation

Avant d'appeler l'API Digital Credentials, vérifiez si l'opérateur de l'utilisateur est compatible avec cette fonctionnalité à l'aide de l'API Android Telephony.

Ajoutez l'autorisation READ_PHONE_STATE à votre fichier AndroidManifest.xml.

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

Avec cet ID, vérifiez la compatibilité avec TS.43 auprès de votre backend d'agrégateur. Si l'opérateur de cet utilisateur n'est pas compatible, vous pouvez utiliser une autre méthode, comme les SMS, car l'appel de l'API Digital Credentials renvoie un résultat vide. Sinon, poursuivez avec le flux Digital Credentials.

Le processus de bout en bout suit généralement ces étapes :

  1. Demander des paramètres DCQL (Digital Credential Query Language) à un agrégateur : appelez un ou plusieurs agrégateurs et demandez un ensemble de paramètres DCQL. Le DCQL vous permet de spécifier les identifiants numériques exacts dont vous avez besoin pour chaque agrégateur.
  2. Créer la requête OpenID4VP : à partir du backend de votre application, créez la requête OpenID4VP, en incluant les paramètres DCQL de l'agrégateur. Ensuite, envoyez la requête OpenID4VP à votre application cliente.

  3. Appeler l'API Credential Manager : dans votre application cliente, utilisez l'API Credential Manager pour envoyer la requête OpenID4VP au système d'exploitation. En réponse, vous recevez un objet de réponse OpenID4VP contenant le TS.43 Digital Credential. Cet identifiant est chiffré et ne peut être déchiffré que par l'agrégateur associé. Après avoir reçu le jeton de l'opérateur, envoyez la réponse de votre application cliente au backend de l'application.

  4. Valider la réponse : dans le backend de votre application, validez la réponse OpenID4VP.

  5. Échange contre un numéro de téléphone : depuis le backend de votre application, envoyez le TS.43 Digital Credential à l'agrégateur. L'agrégateur valide les identifiants et renvoie le numéro de téléphone validé.

Image montrant le flux d'une demande de validation de numéro de téléphone
Figure 1.Cycle de vie d'une demande de vérification du numéro de téléphone, de la demande de paramètres par le backend du vérificateur à un agrégateur jusqu'au renvoi d'un numéro de téléphone validé.

Demander des paramètres DCQL à un agrégateur

Depuis le backend de votre application, envoyez une requête à l'agrégateur pour obtenir un objet d'identifiant DCQL (Digital Credential Query Language). Veillez à fournir un nonce et un ID de requête dans votre requête. L'agrégateur renvoie l'objet d'identifiant DCQL, qui présente une structure semblable à celle-ci :

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

Créer la requête OpenID4VP

Tout d'abord, à partir du backend de votre application, créez un objet dcql_query en plaçant l'objet d'identifiant DCQL dans un tableau credentials imbriqué dans un objet dcql_query, comme illustré dans l'exemple suivant :

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

Ensuite, créez une requête OpenID4VP avec la structure suivante :

{
  "protocol": "openid4vp-v1-unsigned",
  "data": {
    "response_type": "vp_token",
    "response_mode": "dc_api",
    "nonce": "...",
    "dcql_query": { ... }
  }
}
  • protocol : doit être défini sur openid4vp-v1-unsigned pour les demandes de validation de numéro de téléphone.
  • response_type et response_mode : constantes indiquant le format de la requête avec les valeurs fixes vp_token et dc_api, respectivement.
  • nonce : valeur unique générée par votre backend pour chaque requête. Le nonce de l'objet d'identifiant DCQL de l'agrégateur doit correspondre à ce nonce.
  • dcql_query : dans ce cas, utilisez dcql_query pour spécifier qu'un TS.43 Digital Credential est demandé. Vous pouvez également demander d'autres identifiants numériques ici.

Ensuite, encapsulez la requête OpenID4VP dans un objet de requête API DigitalCredential et envoyez-la à l'application cliente.

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

L'extrait de code suivant montre comment générer la requête API 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

Appeler l'API Credential Manager

Dans votre application cliente, appelez l'API Credential Manager avec la requête API DigitalCredential fournie par le backend de votre application.

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 réponse de l'API DigitalCredential contient la réponse OpenID4VP. Voici un exemple de fichier JSON d'identifiants issu du résultat DigitalCredential :

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

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

Depuis votre application cliente, renvoyez la réponse de l'API DigitalCredential au serveur backend, où elle peut être validée et utilisée pour l'échange contre le numéro de téléphone validé avec un agrégateur.

Dans certains cas, la réponse peut contenir une erreur TS.43. La réponse d'erreur est un objet JSON au format de réponse d'erreur OpenID4VP :

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

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

Il existe deux valeurs error_code possibles :

  • invalid_request : indique que la requête est incorrecte.
  • server_error : indique que des échecs se sont produits lors du traitement de la requête. Il peut s'agir d'erreurs locales ou de problèmes TS.43.

Le champ error_description fournit des détails supplémentaires sur le problème.

Valider la réponse Digital Credential

Voici un exemple d'analyse de la réponse et d'exécution de l'étape de validation dans le backend de votre application :

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.

Échanger contre un numéro de téléphone

Depuis le backend de votre application, envoyez le TS.43 Digital Credential validé au point de terminaison de l'agrégateur pour valider les identifiants et recevoir le numéro de téléphone validé.

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

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