Loading...
Loading...
Provides a complete workflow for implementing verified email retrieval on Android Credential Manager API. Use this skill to integrate a secure, OTP-less email verification flow into an Android app. This skill solves the problem of high-friction sign-up processes by leveraging cryptographically verified credentials from trusted providers like Google.
npx skill4agent add android/skills verified-emaildcql_queryUserInfoCredentialGetDigitalCredentialOptionSignUpScreen"Email address""Recover Account""Account Recovery""Forgot password?""Delete Account"signupregistrationcreate_accountforgot_passwordrecoveryverify_emailNavHostcomposableSignUpViewModelAuthViewModelRegistrationRepositoryonCrea teAccountonRecoverAccountvalidateEmailChangePasswordUpdatePaymentDeleteAccountUpdateDetailsEditUserDetailsSdJwtParserJSONObjectVerifiedUserInfobuild.gradledependencies {
implementation("androidx.credentials:credentials:1.7.0-alpha02")
implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02")
}dependencies {
implementation "androidx.credentials:credentials:1.7.0-alpha02"
implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02"
}CredentialManager// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)GetCredentialRequestGetDigitalCredentialOptionrequestJson"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_queryemail_verifiedemail_verifiedhd[!NOTE] Note: Ifisemail_verifiedandtrueis empty in the response, it implies that the account is an authorized Google Account. Currently, Google does not issue verifiable credentials for Google Workspace Accounts. However, thehdfield is present in verifiable credentials issued for non-workspace accounts. You are encouraged to implement handling this field to future-proof your app.hd
email_verifiedUserInfoCredentialopenId4vpRequestGetDigitalCredentialOptionGetCredentialRequestgetCredential()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] Note: There is no equivalent of Sign in with Google'sfor Digital Credentials. If no verifiable credential is found (for example, no eligible account on device), the user will be shown a "No options available" or similar system screen.preferImmediatelyAvailableCredentials
[!IMPORTANT] Important: This step is not for validation. Full cryptographic verification must be performed on your server.
// 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"))
)DigitalCredentialresponseJsonString/*
// 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": ""
}
*/[!NOTE] Note: We highly recommend that after receiving the verified email, you trigger Credential Manager's passkey creation.
vp_tokenresponseJsonStringissSD-JWTcnfkbisshttps://verifiablecredentials-pa.googleapis.com[!NOTE] Note: Use a standard library (such as @sd-jwt/sd-jwt-vc for Node.js) to perform the verification steps as outlined in the OpenID for Verifiable Presentations specification.
noncetry {
// 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
}responseJsonStringnonceisscnf