verified-email について
OpenID4VP を使い、Digital Credentials Verifier API から検証済みメールアドレスを取得する Android クライアント側の統合スキルです。認証情報のリクエストとレスポンス解析を扱い、暗号検証はバックエンドで行います。
主な機能
- 01OpenID4VP 認証情報リクエストの作成
- 02Credential Manager による認証情報の要求
- 03返された SD-JWT クレームの端末上での解析
- 04サーバー側の認証情報検証に関する指針
- 05アカウントフローへの統合ガイダンス
活用シーン
- 01アカウント作成時のメール確認
- 02アカウント復旧時のメール所有確認
- 03機密操作時の再認証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ android/ skills --skill verified-emailインストール後、Claude Code で/verified-emailを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/android/identity/credential-manager/index.mdreferences/android/identity/digital-credentials/credential-verifier.mdemail-verification-implementation.mdemail-verification.mdindex.mdreferences/android/identity/passkeys/create-passkeys.mdreferences/android/identity/sign-in/credential-manager-webview.md詳細
基本事項
- デジタル認証情報の概要:暗号学的に検証可能なドキュメントと、Credential Manager の役割について説明します。
- 用語集:
dcql_query、UserInfoCredential、GetDigitalCredentialOptionの定義です。
標準 & 使用例
- OpenID4VP 標準:デジタル認証情報のリクエストを作成するために使用される仕様です。
- デジタル認証情報のデモ:リクエストの例と、クロスプラットフォームのテストツールです。
- W3C Verifiable Credentials:暗号学的に保護されたクレームのデータモデルです。
- SD-JWT:レスポンスに使用される選択的開示 JSON Web Token 形式です。
- mdoc:モバイルドキュメントに関する ISO/IEC 18013-5 標準です。
要件
- SDK バージョン:SDK 28(Android 9)以降が必要です。
- GMS バージョン:Google Play services バージョン 25.49.x 以降が必要です。
ユースケース
メールアドレスの検証は、次のユースケースに適用できます。
- アカウント作成/サインアップ:手動でのメールアドレス検証を省略し、手間を減らします。
- アカウント復旧:復旧フロー中にメールアドレスの所有者を安全に確認します。
- 再認証:初回のサインイン方法に依存せず、リスクの高い操作に柔軟な検証を提供します。
制限事項 & 注意点
- Workspace アカウント:Google は Google Workspace アカウント向けに検証可能な認証情報を発行しません。
- 鮮度:
@gmail.com以外のアドレスについては、Google はアカウント作成時にメールアドレスを検証しますが、鮮度のクレームはありません。OTP などの追加チャレンジを実装してください。
対象範囲 & 前提条件
重要:このスキルでは、Android クライアント側の統合のみを扱います。アプリのサーバー側の暗号学的検証ロジックは実装しません。セキュリティを確保するため、返された認証情報のサーバー側検証が必要です。バックエンドに実装してください。
ユースケースに向けたコードベースの調査
プロジェクトのソースコードで次のクエリを使用して、検証済みメールアドレスを実装する対象となるユースケース別の画面を探します。
SignUpScreen"Email address""Recover Account""Account Recovery""Forgot password?""Delete Account"
統合ポイントの特定
この機能を効果的に実装するには、まずコードベース内の関連フローを見つける必要があります。検証済みメールアドレスをさまざまなユースケースに活用するため、次の方法で調査を始めてください。
1. ナビゲーションルートを検索する
アプリで Navigation を使用している場合は、認証に関連するルートやデスティネーションを検索します。
次を探します。
- キーワード:
signup、registration、create_account、forgot_password、recovery、verify_email。 - コードパターン:これらの文字列を使用している
NavHostまたはcomposableのデスティネーションを検索します。
2. 認証の ViewModel を特定する
ユーザー属性、アカウント作成、アカウント復旧を処理するビジネスロジックを探します。
- キーワード:
SignUpViewModel、AuthViewModel、RegistrationRepository。 - コードパターン:
onCreateAccount、onRecoverAccount、validateEmailなどのメソッドを探します。
3. 機密性の高い操作における再認証の箇所を探す
再認証のユースケースでは、ユーザーが機密性の高い操作を行う箇所を探します。
- キーワード:
ChangePassword、UpdatePayment、DeleteAccount、UpdateDetails、EditUserDetails
実装に関する重要なポイント
- Digital Credential リクエストを作成し、ユーザーに提示します。
- ドキュメントに記載されているリクエストの JSON 構造に従ってください。
- リクエストをユーザーに提示する際は、結果のクレデンシャルが DigitalCredential であること、および
credential.credentialJsonがresponseJsonStringであることを確認します。 - クライアントからのレスポンスを解析します。
- パスキーがまだ存在しない場合は、作成するオプションを提示します。
- ローカルの
SdJwtParserを使って、生の SD-JWT を解析し、JSONObjectを返すものとします。 - 解析した名前とメールアドレスを格納するために、
VerifiedUserInfoデータクラスを使用します。 - アプリのサーバーサイド検証と解析は開発者が処理するための TODO を残します。
- API 呼び出しが成功したら、ユーザーをホーム画面に移動し、参照のみを目的としてユーザーの詳細を含むスナックバーを表示します。
概要
このガイドでは、OpenID for Verifiable Presentations (OpenID4VP) リクエストを使用して、Digital Credentials Verifier API で検証済みメールアドレスを取得する方法を説明します。
依存関係を追加する
アプリの build.gradle ファイルに、Credential
Manager の以下の依存関係を追加します。
Kotlin
dependencies {
implementation("androidx.credentials:credentials:1.7.0-alpha03")
implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
}
Groovy
dependencies {
implementation "androidx.credentials:credentials:1.7.0-alpha03"
implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03"
}
Credential Manager を初期化する
アプリまたはアクティビティのコンテキストを使って、CredentialManager オブジェクトを作成します。
// アプリまたはアクティビティのコンテキストを使って、
// CredentialManager のクライアントインスタンスを作成します。
private val credentialManager = CredentialManager.create(context)
Digital Credential リクエストを作成する
検証済みメールアドレスをリクエストするには、OpenID for Verifiable Presentations
(OpenID4VP) リクエスト形式の requestJson 文字列を含む
GetDigitalCredentialOption を含めて、GetCredentialRequest を作成します。
OpenID4VP リクエストの JSON は、特定の構造に従う必要があります。現在の
プロバイダーは、外側を "digital": {"requests": [...]} で囲む JSON 構造をサポートしています。
val nonce = generateSecureRandomNonce()
// このリクエストは OpenID4VP 仕様に準拠しています
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(ホストドメイン):レスポンスでは、空になります。
メモ
Note: レスポンスで
email_verifiedがtrueでhdが空の場合、そのアカウントは認可された Google アカウントであることを意味します。Google は Google Workspace アカウントに対して検証可能なクレデンシャルを発行しません。ただし、Workspace 以外のアカウントに対して発行される検証可能なクレデンシャルにはhdフィールドが存在します。アプリの将来性を確保するために、このフィールドを処理する実装を行うことをお勧めします。メールアドレスが @gmail.com 以外の場合、Google アカウントの作成時にこのメールアドレスが検証されていますが、鮮度の主張はありません。そのため、Google 以外のメールアドレスの場合は、ユーザーを検証するために OTP などの追加のチャレンジを検討すべきです。クレデンシャルのスキーマと、email_verifiedなどのフィールドを検証するための具体的なルールについては、Google Identity ガイドを参照してください。 -
nonce: リクエストごとに、暗号学的に安全な一意のランダム値が生成されます。これはリプレイ攻撃を防ぐため、セキュリティ上極めて重要です。
-
UserInfoCredential: この値は、ユーザー属性を含む特定の種類のデジタルクレデンシャルを意味します。これをリクエストに含めることは、メール検証のユースケースを区別するために極めて重要です。
次に、openId4vpRequest JSON を GetDigitalCredentialOption でラップし、GetCredentialRequest を作成して、getCredential() を呼び出します。
メモ
Note:
hdおよびemail_verifiedフィールドは、Credential Manager の組み込み UI ではユーザーに表示されません。これらの非表示フィールドのみでリクエストを行うことはできません。そのようなリクエストの場合、レスポンスはGetCredentialCancellationExceptionとなります。
リクエストをユーザーに提示する
Credential Manager の組み込み UI を使用して、リクエストをユーザーに提示します。
coroutineScope {
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
}
}
メモ
Note: Digital Credentials には、Sign in with Google の
preferImmediatelyAvailableCredentialsに相当するものはありません。検証可能なクレデンシャルが見つからない場合 (たとえば、デバイス上に適格なアカウントがない場合)、ユーザーには「No options available」または同様のシステム画面が表示されます。
クライアントでレスポンスを解析する
警告
警告: 2026 年 8 月以降、レスポンス JSON 形式は W3C 標準に厳密に準拠するよう更新されています。この形式には
dataキーとprotocolキーが含まれ、OpenID4VP のvp_tokenはdataの中にネストされています。一方、従来の形式ではvp_tokenが直接格納されています。移行期間中は、古い実装が段階的に廃止される間、クライアント側の解析とサーバー側の検証で両方の形式に対応するようにしてください。2026 年 8 月以降にメール確認フローの統合を開始するアプリでは、新しい形式のみを使用する必要があります。
レスポンスを受け取った後、クライアントで事前解析を行うことができます。 たとえば、ユーザーの名前を表示するなど、UI をすぐに更新するのに役立ちます。
重要
重要: この手順は検証を目的としたものではありません。完全な暗号学的検証はサーバーで実行する必要があります。
次のコードは、生の Selective Disclosure JWT (SD-JWT) を抽出し、ヘルパーを使用してそのクレームをデコードします。
// 1. 外側の JSON ラッパーを解析して `vp_token` を取得します
val responseData = JSONObject(responseJsonString)
val dataObject = responseData.getJSONObject("data")
val vpToken = dataObject.getJSONObject("vp_token")
// 2. 生の SD-JWT 文字列を抽出します
val credentialId = vpToken.keys().next()
val rawSdJwt = vpToken.getJSONArray(credentialId).getString(0)
// 3. パーサーを使用して検証済みクレームを取得します
// サーバー側での検証/解析を強く推奨します。
// SdJwtParser.kt のサンプルのようなローカルパーサーを想定しています
val claims = SdJwtParser.parse(rawSdJwt)
Log.d("TAG", "Parsed Claims: ${claims.toString(2)}")
// 4. 実際のデータを使って VerifiedUserInfo オブジェクトを作成します
val userInfo = VerifiedUserInfo(
email = claims.getString("email"),
displayName = claims.optString("name", claims.getString("email"))
)
レスポンスを処理する
Credential Manager API は、DigitalCredential
レスポンスを返します。
次に、生の responseJsonString の例と、メールの検証に加えて追加のメタデータも取得できる、内側の SD-JWT を解析した後のクレームの例を示します。
/*
// credential.credentialJson からの生の JSON レスポンスの例:
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"vp_token": {
// このキーは dcql_query で設定した 'id' と一致します
"user_info_query": [
// SD-JWT 文字列(発行者 JWT ~ 開示情報 ~ キーバインディング JWT)
"eyJhbGciOiJ...~WyI...IiwgImVtYWlsIiwgInVzZXJAZXhhbXBsZS5jb20iXQ~...~eyJhbGciOiJ..."
]
}
}
}
// サーバーで SD-JWT から解析・検証されたクレームの例:
{
"cnf": {
"jwk": {..}
},
"exp": 1775688222,
"iat": 1775083422,
"iss": "https://verifiablecredentials-pa.googleapis.com",
"vct": "UserInfoCredential",
"email": "[email protected]",
"email_verified": true,
"given_name": "Jane",
"family_name": "Doe",
"name": "Jane Doe",
"picture": "http://example.com/janedoe/me.jpg",
"hd": ""
}
*/
重要
重要: 検証済みメールアドレスを受け取った後に、Credential Manager のパスキー作成を開始することを強くおすすめします。
アカウント作成時のサーバー側検証
取得したメールアドレスは暗号学的に検証済みであるため、メールの OTP 検証ステップを省略でき、登録時の手間を大幅に減らし、コンバージョンの向上も期待できます。この処理はサーバー側で行うのが最適です。クライアントは、vp_token を含む生のレスポンスと元の nonce を新しいサーバーエンドポイントに送信します。
検証するには、アカウントを作成したりユーザーをログインさせたりする前に、完全な responseJsonString をサーバーに送信し、暗号学的な検証を行う必要があります。
デジタル認証情報は、サーバーに対して次の 2 つの重要な検証レベルを提供します。
- データの真正性:発行者の(
iss)URL とSD-JWT署名を検証することで、信頼できる機関がこのデータを発行したことを証明します。 - 提示者の本人性:
cnfフィールドと Key Binding(kb)署名を検証することで、認証情報が最初に発行されたものと同じデバイスから共有されていることを確認し、傍受されたり別のデバイスで使用されたりするのを防ぎます。
サーバー側の検証では、次のことを行う必要があります。
- 発行者を検証する:
iss(発行者)フィールドがhttps://verifiablecredentials-pa.googleapis.comと一致することを確認します。 - 署名を検証する:https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks にある公開鍵(JWK)を使用して、SD-JWT の署名を確認します。
メモ
注:OpenID for Verifiable Presentations 仕様で説明されている検証手順を実行するには、標準ライブラリ(Node.js の場合は @sd-jwt/sd-jwt-vc など)を使用してください。
セキュリティを完全に確保するため、リプレイ攻撃を防ぐ目的で nonce も検証してください。
これらの手順を組み合わせることで、サーバーはデータの真正性と提示者の本人性の両方を検証できます。これにより、新しいアカウントを作成する前に、認証情報が傍受されたり偽造されたりしていないことを確認できます。
警告
警告:クライアントでのレスポンスの解析で説明したとおり、2026 年 8 月から、レスポンス JSON の形式が W3C 標準に準拠するよう更新されます。旧実装が段階的に廃止されるまでの移行期間中は、クライアント側の解析とサーバー側の検証で両方の形式を処理できるようにしてください。2026 年 8 月以降にメール検証フローの統合を開始するアプリでは、新しい形式のみを使用する必要があります。
try {
// 生の認証情報レスポンスと元の nonce をサーバーに送信します。
// サーバーはレスポンスを検証する必要があります。createAccountWithVerifiedCredentials
// は、サーバー側での検証とアカウント作成を行う、各 RP 独自の実装です。
val serverResponse = createAccountWithVerifiedCredentials(responseJsonString, nonce)
// サーバーから新しいアカウント情報(メールアドレス、名前など)が返されます。
val claims = JSONObject(serverResponse.json)
val userInfo = VerifiedUserInfo(
email = claims.getString("email"),
displayName = claims.optString("name", claims.getString("email"))
)
// レスポンスを処理します。実装は開発者に委ねられます。
} catch (e: Exception) {
// 例外を処理します。実装は開発者に委ねられます。
}
パスキーの作成
アカウントを作成した後の次のステップとして、任意ではありますが、そのアカウント用のパスキーを作成することを強くおすすめします。これにより、ユーザーは安全なパスワードレス方式でサインインできます。このフローは標準的なパスキー登録と同じです。
WebView のサポート
このフローを WebView で動作させるには、開発者は
引き継ぎを円滑に行うためのJavaScript ブリッジ(JS Bridge)を実装する必要があります。このブリッジにより、
WebView オブジェクトからネイティブアプリに通知できるようになり、ネイティブアプリはその後、
Credential Manager API を実際に呼び出せます。
関連項目
重要なセキュリティガイドライン
メールアドレス検証フローの完全性を維持するため、次のセキュリティ要件を必ず満たしてください。
- サーバー側での検証:アカウント作成など、セキュリティ上重要な操作において、クライアントで解析したクレームを決して信頼しないでください。完全な未加工の
responseJsonStringと元のnonceをアプリのサーバーに送信し、完全な検証を行ってください。 - Nonce の完全性:リクエストごとに一意で暗号学的に安全な nonce を生成し、リプレイ攻撃を防ぐため、複数のリクエストで nonce を決して再利用しないでください。
- 暗号学的検証:アプリのサーバーは、発行者(
iss)フィールド、SD-JWT 署名、およびcnfフィールドを使用した提示者の ID を検証する必要があります。