Panduan ini menjelaskan cara menerapkan pengambilan email terverifikasi menggunakan Digital Credentials Verifier API melalui permintaan OpenID for Verifiable Presentations (OpenID4VP).
Menambahkan dependensi
Di file build.gradle aplikasi Anda, tambahkan dependensi berikut untuk Pengelola
Kredensial:
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" }
Menginisialisasi Credential Manager
Gunakan konteks aplikasi atau aktivitas Anda untuk membuat objek CredentialManager.
// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)
Buat permintaan Kredensial Digital
Untuk meminta email terverifikasi, buat GetCredentialRequest yang berisi
GetDigitalCredentialOption. Opsi ini memerlukan requestJson
string yang diformat sebagai permintaan OpenID untuk Presentasi yang Dapat Diverifikasi (OpenID4VP).
JSON permintaan OpenID4VP harus mengikuti struktur tertentu. Penyedia saat ini mendukung struktur JSON dengan wrapper "digital": {"requests":
[...]} luar.
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))
Permintaan berisi informasi utama berikut:
Kueri DCQL:
dcql_querymenentukan jenis kredensial dan klaim yang diminta (email_verified). Anda dapat meminta klaim lain untuk menentukan tingkat verifikasi. Beberapa kemungkinan klaim adalah sebagai berikut:email_verified: Dalam respons, ini adalah Boolean yang menunjukkan apakah email diverifikasi.hd(domain yang dihosting): Dalam respons, kolom ini kosong.
Jika email tersebut bukan @gmail.com, Google memverifikasi email ini saat Akun Google dibuat, tetapi tidak ada klaim keaktualan. Oleh karena itu, untuk email non-Google, Anda harus mempertimbangkan tantangan tambahan, seperti OTP, untuk memverifikasi pengguna. Untuk memahami skema kredensial dan aturan khusus untuk memvalidasi kolom seperti
email_verified, lihat panduan Identitas Google.nonce: Nilai acak yang unik dan aman secara kriptografis dibuat untuk setiap permintaan. Hal ini sangat penting untuk keamanan, karena mencegah serangan replay.
UserInfoCredential: Nilai ini menyiratkan jenis kredensial digital tertentu yang berisi atribut pengguna. Menyertakan ini dalam permintaan sangat penting untuk membedakan kasus penggunaan verifikasi email.
Selanjutnya, bungkus JSON openId4vpRequest dalam GetDigitalCredentialOption, buat
GetCredentialRequest, dan panggil getCredential().
Menampilkan permintaan kepada pengguna
Menampilkan permintaan kepada pengguna, menggunakan UI bawaan Credential Manager.
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
}
Mem-parsing respons di klien
Setelah menerima respons, Anda dapat melakukan penguraian awal pada klien. Hal ini berguna untuk segera memperbarui UI, misalnya, dengan menampilkan nama pengguna.
Kode berikut mengekstrak JWT Pengungkapan Selektif (SD-JWT) mentah dan menggunakan helper untuk mendekode klaimnya.
// 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"))
)
Menangani respons
Credential Manager API akan menampilkan respons DigitalCredential.
Berikut adalah contoh tampilan responseJsonString mentah, dan tampilan klaim setelah mengurai SD-JWT dalam yang memberikan metadata tambahan serta email terverifikasi:
/*
// 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": "[email protected]",
"email_verified": true,
"given_name": "Jane",
"family_name": "Doe",
"name": "Jane Doe",
"picture": "http://example.com/janedoe/me.jpg",
"hd": ""
}
*/
Validasi sisi server untuk pembuatan akun
Karena email yang diambil diverifikasi secara kriptografis, Anda dapat menghilangkan langkah verifikasi OTP email, sehingga mengurangi hambatan pendaftaran secara signifikan dan berpotensi meningkatkan konversi. Proses ini sebaiknya ditangani di server Anda. Klien
mengirimkan respons mentah (yang berisi vp_token) dan nonce asli ke endpoint
server baru.
Untuk verifikasi, aplikasi Anda harus mengirimkan responseJsonString lengkap ke server Anda untuk validasi kriptografi sebelum membuat akun atau membuat pengguna login.
Kredensial digital memberikan dua tingkat verifikasi penting untuk server Anda:
- Keaslian data: Memverifikasi URL penerbit (
iss) dan tanda tanganSD-JWTmembuktikan bahwa otoritas tepercaya menerbitkan data ini. - Identitas presenter: Memverifikasi kolom
cnfdan tanda tangan Pengikatan Kunci (kb) mengonfirmasi bahwa kredensial dibagikan oleh perangkat yang sama dengan yang menerbitkannya, sehingga mencegah kredensial dicegat atau digunakan di perangkat lain.
Validasi di server harus mencapai hal berikut:
- Verifikasi penerbit: Pastikan kolom
iss(penerbit) cocok denganhttps://verifiablecredentials-pa.googleapis.com. - Verifikasi tanda tangan: Periksa tanda tangan SD-JWT menggunakan kunci publik (JWK) yang tersedia di https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.
Untuk keamanan penuh, pastikan Anda juga memvalidasi nonce untuk mencegah serangan replay.
Dengan menggabungkan langkah-langkah ini, server Anda dapat memvalidasi keaslian data dan identitas presenter, sehingga memastikan kredensial tidak dicegat atau dipalsukan sebelum menyediakan akun baru.
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
}
Pembuatan kunci sandi
Langkah berikutnya yang opsional, tetapi sangat direkomendasikan setelah menyediakan akun adalah membuat kunci sandi untuk akun tersebut. Hal ini memberikan metode login tanpa sandi yang aman bagi pengguna. Alur ini sama dengan pendaftaran kunci sandi standar.
Dukungan WebView
Agar alur berfungsi di WebView, developer harus menerapkan JavaScript bridge (JS Bridge) untuk memfasilitasi pengalihan. Jembatan ini memungkinkan Webview untuk memberi sinyal ke aplikasi native, yang kemudian dapat melakukan panggilan sebenarnya ke Credential Manager API.