電話番号の確認を実装する

このガイドでは、DigitalCredential API を使用して、ユーザーの確認済み電話番号を取得する方法について詳しく説明します。このプロセスには次の 2 つのステップがあります。

  1. 事前チェック: Android Telephony API をクエリして、ユーザーの携帯通信会社がこの API をサポートしているかどうかを確認します。携帯通信会社がサポートされていない場合は、通常の SMS フローにフォールバックします。
  2. TS.43 token をリクエストする: クライアント アプリ(「検証ツール」)が、ユーザーのデバイスから一時的な TS.43 トークンをリクエストします。TS.43 token は、ユーザーの ID を表す携帯通信会社発行の認証情報です。
  3. トークンを電話番号と交換する: アプリのバックエンドが、アグリゲータまたは携帯通信会社と TS.43 token を交換して、ユーザーの確認済みの電話番号を取得します。

Android バージョンの互換性

Phone Number Verification API は、Android 10(API レベル 29)以降でサポートされています。

前提条件

DigitalCredential API を使用して電話番号の確認を実装するには、アグリゲータまたは携帯通信会社のアカウントが必要です。アグリゲータは携帯通信会社とやり取りし、アプリに必要な API サーフェスを、通常は課金対象の Cloud APIs エンドポイントとして提供します。このガイドでは、アグリゲーターと携帯通信会社の両方をまとめてアグリゲーターと呼びます。

また、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 を使用して、ユーザーの携帯通信会社がこの機能をサポートしているかどうかを確認します。

AndroidManifest.xml ファイルに READ_PHONE_STATE 権限を追加します。

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

この ID を使用して、アグリゲータ バックエンドで TS.43 のサポートを確認します。このユーザーの携帯通信会社がサポートされていない場合は、Digital Credentials API を呼び出すと空の結果が返されるため、SMS などの代替方法にフォールバックできます。それ以外の場合は、デジタル認証情報のフローを続行します。

エンドツーエンドのプロセスは一般的に、次のステップで行われます。

  1. アグリゲータから DCQL(Digital Credential Query Language)パラメータをリクエストする: 1 つ以上のアグリゲータを呼び出し、DCQL パラメータのセットをリクエストします。DCQL を使用すると、各アグリゲータから必要なデジタル認証情報を正確に指定できます。
  2. OpenID4VP リクエストを作成する: アプリのバックエンドから、アグリゲータの DCQL パラメータを含む OpenID4VP リクエストを作成します。次に、OpenID4VP リクエストをクライアント アプリに送信します。

  3. Credential Manager API を呼び出す: クライアント アプリで、Credential Manager API を使用して OpenID4VP リクエストをオペレーティング システムに送信します。レスポンスとして、TS.43 Digital Credential を含む OpenID4VP レスポンス オブジェクトが返されます。この認証情報は暗号化されており、関連付けられたアグリゲータのみが復号できます。携帯通信会社トークンを受け取ったら、クライアント アプリからアプリのバックエンドにレスポンスを送信します。

  4. レスポンスを検証する: アプリのバックエンドで、OpenID4VP レスポンスを検証します。

  5. 電話番号と交換する: アプリのバックエンドから、TS.43 Digital Credential をアグリゲータに送信します。アグリゲータは認証情報を検証し、確認済みの電話番号を返します。

電話番号の確認リクエストのフローを示す画像
図 1.電話番号の確認リクエストのライフサイクル。検証ツールのバックエンドがアグリゲータにパラメータをリクエストすることから始まり、確認済みの電話番号が返されることで終了します。

アグリゲータに DCQL パラメータをリクエストする

アプリのバックエンドから、アグリゲータに Digital Credential Query Language(DCQL)認証情報オブジェクトのリクエストを送信します。リクエストには、ノンスとリクエスト ID を指定してください。アグリゲータは、次のような構造の 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 オブジェクト内にネストされた credentials 配列に DCQL 認証情報オブジェクトを配置して、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 を呼び出す

クライアント アプリで、アプリのバックエンドから提供された DigitalCredential API リクエストを使用して、Credential Manager 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 レスポンスが含まれます。DigitalCredential の結果から得られる一般的な認証情報 JSON は次のとおりです。

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

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

クライアント アプリから、DigitalCredential API レスポンスをバックエンド サーバーに送信します。バックエンド サーバーでは、このレスポンスを検証し、アグリゲータとの確認済みの電話番号の交換に使用できます。

場合によっては、レスポンスに TS.43 エラーが含まれることがあります。エラー レスポンスは、OpenID4VP エラー レスポンス形式に準拠した JSON オブジェクトです。

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

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

error_code には次の 2 つの値があります。

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