Habilidades do Android
Ver no GitHubRecuperar e-mail verificado
android skills add verified-emailVisão geral
Este guia descreve como implementar a recuperação de e-mail verificado usando a API Digital Credentials Verifier por uma solicitação do OpenID para apresentações verificáveis (OpenID4VP).
Adicionar dependências
No arquivo build.gradle do app, adicione as seguintes dependências para o Gerenciador de
credenciais:
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" }
Inicializar o Credential Manager
Use o contexto do app ou da atividade para criar um objeto CredentialManager.
// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)
Criar a solicitação de credencial digital
Para solicitar um e-mail de verificação, crie um GetCredentialRequest
contendo um GetDigitalCredentialOption. Essa opção requer uma string requestJson formatada como uma solicitação do OpenID para apresentações verificáveis (OpenID4VP).
O JSON da solicitação do OpenID4VP precisa seguir uma estrutura específica. Os provedores atuais são compatíveis com uma estrutura JSON com um wrapper "digital": {"requests":
[...]} externo.
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))
A solicitação contém as seguintes informações principais:
Consulta DCQL: o
dcql_queryespecifica o tipo de credencial e as declarações solicitadas (email_verified). É possível solicitar outras declarações para determinar o nível de verificação. Algumas possíveis declarações são as seguintes:email_verified: na resposta, é um booleano que indica se o e-mail foi verificado.hd(domínio hospedado): na resposta, esse campo fica vazio.
nonce: um valor aleatório exclusivo e criptograficamente seguro é gerado para cada solicitação. Isso é fundamental para a segurança, porque evita ataques repetidos.
UserInfoCredential: esse valor implica um tipo específico de credencial digital que contém atributos do usuário. Incluir isso na solicitação é fundamental para distinguir o caso de uso de verificação de e-mail.
Em seguida, envolva o JSON openId4vpRequest em um GetDigitalCredentialOption, crie
um GetCredentialRequest e chame getCredential().
Apresentar a solicitação ao usuário
Apresente a solicitação ao usuário usando a interface integrada do Gerenciador de Credenciais.
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
}
}
Analisar a resposta no cliente
Depois de receber a resposta, você pode realizar uma análise preliminar no cliente. Isso é útil para atualizar imediatamente a interface, por exemplo, mostrando o nome do usuário.
O código a seguir extrai o JWT de divulgação seletiva (SD-JWT) bruto e usa um auxiliar para decodificar as declarações.
// 1. Parse the outer JSON wrapper to get the `vp_token`
val responseData = JSONObject(responseJsonString)
val dataObject = responseData.getJSONObject("data")
val vpToken = dataObject.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"))
)
Processar a resposta
A API Credential Manager vai retornar uma resposta
DigitalCredential.
Confira a seguir um exemplo de como é o responseJsonString bruto e
como são as declarações após a análise do SD-JWT interno, em que você também recebe
metadados adicionais com o e-mail de verificação:
/*
// Example of the raw JSON response from credential.credentialJson:
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"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": ""
}
*/
Validação do lado do servidor para criação de conta
Como o e-mail recuperado é verificado criptograficamente, é possível omitir a etapa de verificação de OTP por e-mail, reduzindo significativamente o atrito de inscrição e, possivelmente, aumentando a conversão. É melhor que esse processo seja feito no seu servidor. O cliente
envia a resposta bruta (contendo o vp_token) e o valor de uso único original para um
novo endpoint do servidor.
Para verificação, o aplicativo precisa enviar o responseJsonString completo ao
servidor para validação criptográfica antes de criar uma conta ou fazer login
do usuário.
A credencial digital oferece dois níveis críticos de verificação para seu servidor:
- Autenticidade dos dados: a verificação do URL do emissor (
iss) e da assinaturaSD-JWTprova que uma autoridade confiável emitiu esses dados. - Identidade do apresentador: a verificação do campo
cnfe da assinatura da vinculação de chave (kb) confirma que a credencial está sendo compartilhada pelo mesmo dispositivo para o qual foi emitida originalmente, impedindo que ela seja interceptada ou usada em outro dispositivo.
A validação no servidor precisa:
- Verificar o emissor: verifique se o campo
iss(emissor) corresponde ahttps://verifiablecredentials-pa.googleapis.com. - Verificar assinatura: confira a assinatura do SD-JWT usando as chaves públicas (JWKs) disponíveis em https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.
Para garantir a segurança total, valide também o nonce para evitar
ataques de repetição.
Ao combinar essas etapas, seu servidor pode validar a autenticidade dos dados e a identidade do apresentador, garantindo que a credencial não tenha sido interceptada ou falsificada antes de provisionar a nova conta.
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
}
Criação de chave de acesso
Uma próxima etapa opcional, mas altamente recomendada, após o provisionamento de uma conta é criar uma chave de acesso para ela imediatamente. Isso oferece um método seguro e sem senha para o usuário fazer login. Esse fluxo é idêntico ao registro padrão de uma chave de acesso.
Suporte à WebView
Para que o fluxo funcione em um WebView, os desenvolvedores precisam implementar uma
ponte JavaScript (ponte JS) para facilitar a transferência. Essa ponte
permite que o objeto WebView sinalize o app nativo, que pode realizar a
chamada real para a API Credential Manager.