คู่มือนี้อธิบายวิธีใช้ Digital Credentials Verifier API เพื่อดึงอีเมลที่ยืนยันแล้วผ่านคำขอ OpenID for Verifiable Presentations (OpenID4VP)
เพิ่มทรัพยากร Dependency
เพิ่มทรัพยากร Dependency ต่อไปนี้สำหรับ Credential Manager ลงในไฟล์ build.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" }
เริ่มต้นใช้งาน Credential Manager
ใช้บริบทของแอปหรือกิจกรรมเพื่อสร้างออบเจ็กต์ CredentialManager
// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)
สร้างคำขอข้อมูลเข้าสู่ระบบดิจิทัล
หากต้องการขออีเมลที่ยืนยันแล้ว ให้สร้าง GetCredentialRequest
ที่มี GetDigitalCredentialOption ตัวเลือกนี้ต้องใช้สตริง requestJson ที่จัดรูปแบบเป็นคำขอ OpenID for Verifiable Presentations (OpenID4VP)
JSON ของคำขอ OpenID4VP ต้องเป็นไปตามโครงสร้างที่เฉพาะเจาะจง ผู้ให้บริการปัจจุบัน รองรับโครงสร้าง JSON ที่มี Wrapper ด้านนอก "digital": {"requests":
[...]}
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โปรดดูคู่มือข้อมูลประจำตัวของ Googlenonce: ระบบจะสร้างค่าสุ่มที่ปลอดภัยด้วยการเข้ารหัสลับที่ไม่ซ้ำกันสำหรับ แต่ละคำขอ ค่านี้มีความสำคัญอย่างยิ่งต่อความปลอดภัย เนื่องจากจะป้องกันการโจมตีแบบ Replay
UserInfoCredential: ค่านี้หมายถึงข้อมูลเข้าสู่ระบบดิจิทัลประเภทหนึ่งที่มีแอตทริบิวต์ของผู้ใช้ การรวมค่านี้ไว้ในคำขอมีความสำคัญอย่างยิ่งในการแยกแยะกรณีการใช้งานการยืนยันอีเมล
จากนั้นใส่ JSON ของ openId4vpRequest ใน GetDigitalCredentialOption สร้าง GetCredentialRequest แล้วเรียก getCredential()
แสดงคำขอต่อผู้ใช้
แสดงคำขอต่อผู้ใช้โดยใช้ UI ในตัวของ 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
}
แยกวิเคราะห์การตอบกลับในไคลเอ็นต์
หลังจากได้รับคำตอบแล้ว คุณสามารถแยกวิเคราะห์เบื้องต้นในไคลเอ็นต์ได้ ซึ่งจะเป็นประโยชน์ในการอัปเดต UI ทันที เช่น การแสดงชื่อของผู้ใช้
โค้ดต่อไปนี้จะแยก JWT (SD-JWT) ของการเปิดเผยข้อมูลแบบเลือกสรรแบบดิบและใช้ฟังก์ชัน Helper เพื่อถอดรหัสการอ้างสิทธิ์
// 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 ทางอีเมลได้ ซึ่งจะช่วยลดความยุ่งยากในการลงชื่อสมัครใช้ได้อย่างมากและอาจเพิ่ม Conversion ได้ ระบบควรจัดการกระบวนการนี้บนเซิร์ฟเวอร์ ไคลเอ็นต์จะส่งการตอบกลับแบบดิบ (ที่มี vp_token) และ nonce เดิมไปยังปลายทางเซิร์ฟเวอร์ใหม่
สำหรับการยืนยัน แอปพลิเคชันของคุณต้องส่ง responseJsonString แบบเต็มไปยังเซิร์ฟเวอร์เพื่อทำการยืนยันด้วยการเข้ารหัสลับก่อนที่จะสร้างบัญชีหรือบันทึกการเข้าสู่ระบบของผู้ใช้
ข้อมูลเข้าสู่ระบบดิจิทัลมีการยืนยัน 2 ระดับที่สำคัญสำหรับเซิร์ฟเวอร์
- ความถูกต้องของข้อมูล: การยืนยัน URL ของผู้ออก (
iss) และลายเซ็นSD-JWTจะพิสูจน์ว่าข้อมูลนี้ออกโดยหน่วยงานที่เชื่อถือได้ - ข้อมูลประจำตัวของผู้แสดง: การยืนยันช่อง
cnfและลายเซ็นการผูกคีย์ (kb) จะยืนยันว่าข้อมูลเข้าสู่ระบบได้รับการแชร์โดยอุปกรณ์เครื่องเดียวกับที่ออกข้อมูลเข้าสู่ระบบดังกล่าวในตอนแรก ซึ่งจะป้องกันไม่ให้มีการดักจับหรือใช้ข้อมูลเข้าสู่ระบบในอุปกรณ์เครื่องอื่น
การตรวจสอบบนเซิร์ฟเวอร์ต้องดำเนินการต่อไปนี้
- ยืนยันผู้ออก: ตรวจสอบว่าช่อง
iss(ผู้ออก) ตรงกับhttps://verifiablecredentials-pa.googleapis.com - ยืนยันลายเซ็น: ตรวจสอบลายเซ็นของ SD-JWT โดยใช้คีย์สาธารณะ (JWK) ที่มีอยู่ที่ https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks
ตรวจสอบว่าคุณได้ยืนยัน nonce ด้วยเพื่อป้องกันการโจมตีแบบ Replay
การรวมขั้นตอนเหล่านี้เข้าด้วยกันจะช่วยให้เซิร์ฟเวอร์ยืนยันได้ทั้งความถูกต้องของข้อมูลและข้อมูลประจำตัวของผู้แสดง เพื่อให้มั่นใจว่าไม่มีการดักจับหรือปลอมแปลงข้อมูลเข้าสู่ระบบก่อนที่จะจัดสรรบัญชีใหม่
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 bridge (JS Bridge) เพื่อช่วยในการส่งต่อ Bridge นี้จะช่วยให้ WebView ส่งสัญญาณไปยังแอปที่มาพร้อมเครื่อง ซึ่งจะเรียกใช้ Credential Manager API จริงได้