Digital Credentials API を使用してメールアドレスの確認を実装する

このガイドでは、OpenID for Verifiable Presentations(OpenID4VP)リクエストを介して Digital Credentials Verifier API を使用して、確認済みのメールアドレスの取得を実装する方法について説明します。

依存関係を追加する

アプリの build.gradle ファイルに、Credential Manager の次の依存関係を追加します。

Kotlin

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

Groovy

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

認証情報マネージャーを初期化する

アプリまたはアクティビティのコンテキストを使用して CredentialManager オブジェクトを作成します。

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

デジタル認証情報リクエストを作成する

確認済みのメールをリクエストするには、GetDigitalCredentialOption を含む GetCredentialRequest を作成します。このオプションには、OpenID for Verifiable Presentations(OpenID4VP)リクエストとしてフォーマットされた requestJson 文字列が必要です。

OpenID4VP リクエスト JSON は特定の構造に従う必要があります。現在のプロバイダは、外側の "digital": {"requests": [...]} ラッパーを含む JSON 構造をサポートしています。

        val nonce = generateSecureRandomNonce()

// This request follows the OpenID4VP spec
        val openId4vpRequest = """
    {
      "requests": [
        {
          "protocol": "openid4vp-v1-unsigned",
          "data": {
            "response_type": "vp_token",
            "response_mode": "dc_api",
            "nonce": "$nonce",
            "dcql_query": {
              "credentials": [
                {
                  "id": "user_info_query",
                  "format": "dc+sd-jwt",
                   "meta": { 
                      "vct_values": ["UserInfoCredential"] 
                   },
                  "claims": [ 
                    {"path": ["email"]}, 
                    {"path": ["name"]},  
                    {"path": ["given_name"]},
                    {"path": ["family_name"]},
                    {"path": ["picture"]},
                    {"path": ["hd"]},
                    {"path": ["email_verified"]}
                  ]
                }
              ]
            }
          }
        }
      ]
    }
    """

        val getDigitalCredentialOption = GetDigitalCredentialOption(requestJson = openId4vpRequest)
        val request = GetCredentialRequest(listOf(getDigitalCredentialOption))

リクエストには次のキー情報が含まれます。

  • DCQL クエリ: dcql_query は、認証情報のタイプとリクエストされているクレーム(email_verified)を指定します。他のクレームをリクエストして、検証レベルを判断できます。考えられるクレームは次のとおりです。

    • email_verified: レスポンスでは、メールが確認済みかどうかを示すブール値です。
    • hd(所有ドメイン): レスポンスでは空です。
  • メールアドレスが @gmail.com 以外の場合、Google アカウントの作成時に Google がこのメールアドレスを確認していますが、鮮度に関する申し立てはありません。そのため、Google 以外のメールの場合は、ユーザーを確認するための追加のチャレンジ(OTP など)を検討する必要があります。認証情報のスキーマと、email_verified などのフィールドを検証するための特定のルールについては、Google Identity ガイドをご覧ください。

  • nonce: リクエストごとに一意の暗号論的に安全な乱数が生成されます。これはリプレイ攻撃を防ぐため、セキュリティ上重要です。

  • UserInfoCredential: この値は、ユーザー属性を含む特定のタイプのデジタル認証情報を意味します。リクエストにこれを含めることは、メール確認のユースケースを区別するうえで重要です。

次に、openId4vpRequest JSON を GetDigitalCredentialOption でラップし、GetCredentialRequest を作成して getCredential() を呼び出します。

リクエストをユーザーに提示する

認証情報マネージャーの組み込み UI を使用して、ユーザーにリクエストを提示します。

try {
    // Requesting Digital Credential from user...
    val result = credentialManager.getCredential(activity, request)

    when (val credential = result.credential) {
        is DigitalCredential -> {
            val responseJsonString = credential.credentialJson

            // Successfully received digital credential response.

            // Next, parse this response and send it to your server.
            // ...
        }

        else -> {
            // handle Unexpected State() - Up to the developer
        }
    }
} catch (e: Exception) {
    // handle exceptions - Up to the developer
}

クライアントでレスポンスを解析する

レスポンスを受信したら、クライアントで予備解析を実行できます。これは、ユーザー名を表示するなど、UI を直ちに更新する場合に便利です。

次のコードは、未加工の Selective Disclosure JWT(SD-JWT)を抽出し、ヘルパーを使用してそのクレームをデコードします。

// 1. Parse the outer JSON wrapper to get the `vp_token`
val responseData = JSONObject(responseJsonString)
val vpToken = responseData.getJSONObject("vp_token")

// 2. Extract the raw SD-JWT string
val credentialId = vpToken.keys().next()
val rawSdJwt = vpToken.getJSONArray(credentialId).getString(0)

// 3. Use your parser to get the verified claims
// Server-side validation/parsing is highly recommended.

// Assumes a local parser like the one in our SdJwtParser.kt sample
val claims = SdJwtParser.parse(rawSdJwt)
Log.d("TAG", "Parsed Claims: ${claims.toString(2)}")

// 4. Create your VerifiedUserInfo object with REAL data
val userInfo = VerifiedUserInfo(
    email = claims.getString("email"),
    displayName = claims.optString("name", claims.getString("email"))
)

レスポンスを処理する

Credential Manager API は DigitalCredential レスポンスを返します。

次の例は、未加工の responseJsonString の形式と、内部 SD-JWT を解析した後のクレームの形式を示しています。検証済みのメールアドレスとともに、追加のメタデータも取得できます。

/*
// Example of the raw JSON response from credential.credentialJson:
{
  "vp_token": {
    // This key matches the 'id' you set in your dcql_query
    "user_info_query": [
      // The SD-JWT string (Issuer JWT ~ Disclosures ~ Key Binding JWT)
      "eyJhbGciOiJ...~WyI...IiwgImVtYWlsIiwgInVzZXJAZXhhbXBsZS5jb20iXQ~...~eyJhbGciOiJ..."
    ]
  }
}

// Example of the parsed and verified claims from the SD-JWT on your server:
{
  "cnf": {
    "jwk": {..}
  },
  "exp": 1775688222,
  "iat": 1775083422,
  "iss": "https://verifiablecredentials-pa.googleapis.com",
  "vct": "UserInfoCredential",
  "email": "jane.doe.246745@gmail.com",
  "email_verified": true,
  "given_name": "Jane",
  "family_name": "Doe",
  "name": "Jane Doe",
  "picture": "http://example.com/janedoe/me.jpg",
  "hd": ""
}
 */

アカウント作成のサーバーサイド検証

取得したメールは暗号で検証されるため、メール OTP の検証ステップを省略できます。これにより、登録時の煩雑さを大幅に軽減し、コンバージョン率を高めることができます。このプロセスはサーバーで処理することをおすすめします。クライアントは、未加工のレスポンス(vp_token を含む)と元のノンスを新しいサーバー エンドポイントに送信します。

検証のため、アプリはアカウントの作成またはユーザーのログインを行う前に、完全な responseJsonString をサーバーに送信して暗号検証を行う必要があります。

デジタル認証情報は、サーバーに対して次の 2 つの重要なレベルの検証を提供します。

  • データの信頼性: 発行者(iss)の URL と SD-JWT 署名を検証することで、信頼できる機関がこのデータを発行したことを証明します。
  • プレゼンターの ID: cnf フィールドとキー バインディング(kb)署名を確認することで、認証情報が最初に発行されたデバイスと同じデバイスによって共有されていることを確認し、別のデバイスで傍受されたり使用されたりすることを防ぎます。

サーバーでの検証では、次のことを行う必要があります。

  • 発行者を確認する: iss(発行者)フィールドが https://verifiablecredentials-pa.googleapis.com と一致していることを確認します。
  • 署名を検証する: https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks で入手可能な公開鍵(JWK)を使用して、SD-JWT の署名を確認します。

セキュリティを確保するには、nonce も検証して、リプレイ攻撃を防ぐようにしてください。

これらの手順を組み合わせることで、サーバーはデータの信頼性とプレゼンターの ID の両方を検証し、新しいアカウントをプロビジョニングする前に、認証情報が傍受またはなりすましされていないことを確認できます。

try {
    // Send the raw credential response and the original nonce to your server.
    // Your server must validate the response. createAccountWithVerifiedCredentials
    // is a custom implementation per each RP for server side verification and account creation.
    val serverResponse = createAccountWithVerifiedCredentials(responseJsonString, nonce)

    // Server returns the new account info (e.g., email, name)
    val claims = JSONObject(serverResponse.json)

    val userInfo = VerifiedUserInfo(
        email = claims.getString("email"),
        displayName = claims.optString("name", claims.getString("email"))
    )

    // handle response - Up to the developer
} catch (e: Exception) {
    // handle exceptions - Up to the developer
}

パスキーの作成

アカウントのプロビジョニング後に強く推奨される次のステップは、そのアカウントのパスキーをすぐに作成することです。これにより、ユーザーは安全なパスワードなしの方法でログインできます。このフローは、標準のパスキー登録と同じです。

WebView のサポート

WebView でフローを動作させるには、デベロッパーがハンドオフを容易にする JavaScript ブリッジ(JS ブリッジ)を実装する必要があります。このブリッジにより、WebView はネイティブ アプリにシグナルを送信できます。ネイティブ アプリは、Credential Manager API への実際の呼び出しを実行できます。

関連ドキュメント