This guide details how to use the DigitalCredential API to obtain verified phone numbers for your users. The process involves two steps:
- Request a
TS.43 token
: Your client app (the "verifier") requests a temporary TS.43 token from the user's device. TheTS.43 token
is a carrier-issued credential that represents the user's identity. - Exchange the token for a phone number: Your app's backend exchanges the
TS.43 token
with an aggregator or a carrier for the user's verified phone number.
Prerequisites
To implement phone number verification with the DigitalCredential API, you need an account with an aggregator. An aggregator interacts with carriers and provides the necessary API surface for your app, usually as a billable cloud API endpoint.
You also need to add the following dependencies to your Gradle build script:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.6.0-alpha05") implementation("androidx.credentials:credentials-play-services-auth:1.6.0-alpha05") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.6.0-alpha05" implementation "androidx.credentials:credentials-play-services-auth:1.6.0-alpha05" }
Implementation
The end-to-end process generally follows these steps:
- Request DCQL (Digital Credential Query Language) parameters from an aggregator: Call one or more aggregators and request a set of DCQL parameters. DCQL lets you specify the exact digital credentials that you need from each aggregator.
Create the OpenID4VP request: From your app's backend, create the OpenID4VP request, while including the DCQL parameters from the aggregator. Then, send the OpenID4VP request to your client app.
Call the Credential Manager API: In your client app, use the Credential Manager API to send the OpenID4VP request to the operating system. In response, you receive an OpenID4VP response object containing the
TS.43 Digital Credential
. This credential is encrypted and can only be decrypted by the associated aggregator. After receiving the carrier token, send the response from your client app to the app's backend.Validate the response: In your app's backend, validate the OpenID4VP response.
Exchange for phone number: From your app's backend, send the
TS.43 Digital Credential
to the aggregator. The aggregator validates the credential and returns the verified phone number.
Request DCQL parameters from an aggregator
From your app's backend, send a request to the aggregator for a Digital Credential Query Language (DCQL) credential object. Make sure to provide a nonce and a request ID in your request. The aggregator returns the DCQL credential object, which has a structure similar to the following:
{
// 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"]
}
]
}
Create the OpenID4VP request
First, from your app's backend, create a dcql_query
object by placing the DCQL
credential object in a credentials
array nested within a dcql_query
object
as shown in the following example:
"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"]
}
]
]
}
Then, create an OpenID4VP request with the following structure:
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"response_type": "vp_token",
"response_mode": "dc_api",
"nonce": "...",
"dcql_query": { ... }
}
}
protocol
: Must be set toopenid4vp-v1-unsigned
for phone number verification requests.response_type
andresponse_mode
: Constants denoting the format of the request with fixed valuesvp_token
anddc_api
, respectively.nonce
: A unique value generated by your backend for each request. The nonce in the aggregator DCQL credential object must match this nonce.dcql_query
: In this case, use thedcql_query
to specify that aTS.43 Digital Credential
is being requested. You can also request other digital credentials here.
Then, wrap the OpenID4VP request in a DigitalCredential API request object and send it to the client app.
{
"requests":
[
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"response_type": "vp_token",
"response_mode": "dc_api",
"nonce": "...",
"dcql_query": { ... }
}
}
]
}
The following snippet demonstrates how to generate the DigitalCredential API request:
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
Call the Credential Manager API
In your client app, make a call to the Credential Manager API, with the DigitalCredential API request provided by your app's backend.
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)
}
}
The DigitalCredential API response contains the OpenID4VP response. A typical
credential json from the DigitalCredential
result is as follows:
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"vp_token": {
"aggregator1": ["eyJhbGciOiAiRVMy..."] # The encrypted TS.43 Digital
# Credential in an array structure.
}
}
}
From your client app, send the DigitalCredential API response back to the backend server where it can be validated and used to exchange for the verified phone number with an aggregator.
Validate the Digital Credential response
The following is an example of how to parse the response and perform the validation step in your app's backend:
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.
Exchange for phone number
From your app's backend, send the validated TS.43 Digital Credential
to the
aggregator's endpoint, to validates the credential and receive the verified
phone number.
def processDigitalCredentialsResponse(response):
# ... prior steps
# Step 3: Call aggregator endpoint to exchange the verified phone number
callAggregatorPnvEndpoint(ts43_digital_credential)