Implementa la verificación por correo electrónico

En esta guía, se describe cómo implementar la recuperación de correos electrónicos verificados con la API de Digital Credentials Verifier a través de una solicitud de OpenID para presentaciones verificables (OpenID4VP).

Cómo agregar dependencias

En el archivo build.gradle de tu app, agrega las siguientes dependencias para el Administrador de credenciales:

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha02")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02")
}

Inicializa el Administrador de credenciales

Usa el contexto de tu app o actividad para crear un objeto CredentialManager.

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

Crea la solicitud de credencial digital

Para solicitar un correo electrónico verificado, crea un GetCredentialRequest que contenga un GetDigitalCredentialOption. Esta opción requiere una cadena requestJson con el formato de una solicitud de OpenID para presentaciones verificables (OpenID4VP).

El JSON de la solicitud de OpenID4VP debe seguir una estructura específica. Los proveedores actuales admiten una estructura JSON con un wrapper externo "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))

La solicitud contiene la siguiente información clave:

  • Consulta de DCQL: El dcql_query especifica el tipo de credencial y las declaraciones que se solicitan (email_verified). Puedes solicitar otras declaraciones para determinar el nivel de verificación. Estas son algunas de las posibles declaraciones:
    • email_verified: En la respuesta, este es un valor booleano que indica si el correo electrónico está verificado.
    • hd (dominio alojado): En la respuesta, este campo está vacío.
  • nonce: Se genera un valor aleatorio único y seguro en términos criptográficos para cada solicitud. Esto es fundamental para la seguridad, ya que evita los ataques de reproducción.
  • UserInfoCredential: Este valor implica un tipo específico de credencial digital que contiene atributos del usuario. Incluir esto en la solicitud es fundamental para distinguir el caso de uso de la verificación por correo electrónico.

A continuación, une el JSON openId4vpRequest en un GetDigitalCredentialOption, crea un GetCredentialRequest y llama a getCredential().

Presenta la solicitud al usuario

Presenta la solicitud al usuario con la IU integrada de 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
}

Analiza la respuesta en el cliente

Después de recibir la respuesta, puedes realizar un análisis preliminar en el cliente. Esto es útil para actualizar la IU de inmediato, por ejemplo, mostrando el nombre del usuario.

En el siguiente código, se extrae el JWT de divulgación selectiva (SD-JWT) sin procesar y se usa un ayudante para decodificar sus reclamaciones.

// 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"))
)

Cómo controlar la respuesta

La API de Credential Manager devolverá una respuesta DigitalCredential.

A continuación, se muestra un ejemplo de cómo se ve el responseJsonString sin procesar y cómo se ven las reclamaciones después de analizar el SD-JWT interno, en el que también obtienes metadatos adicionales junto con el correo electrónico verificado:

/*
// 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": ""
}
 */

Validación del servidor para la creación de cuentas

Como el correo electrónico recuperado se verifica de forma criptográfica, puedes omitir el paso de verificación de OTP por correo electrónico, lo que reduce significativamente la fricción de registro y, posiblemente, aumenta las conversiones. Este proceso se maneja mejor en tu servidor. El cliente envía la respuesta sin procesar (que contiene el vp_token) y el nonce original a un extremo de servidor nuevo.

Para la verificación, tu aplicación debe enviar el responseJsonString completo a tu servidor para la validación criptográfica antes de crear una cuenta o acceder al usuario.

La credencial digital proporciona dos niveles de verificación fundamentales para tu servidor:

  • Autenticidad de los datos: Verificar la URL de la entidad emisora (iss) y la firma de SD-JWT demuestra que una autoridad de confianza emitió estos datos.
  • Identidad del presentador: Verificar el campo cnf y la firma de vinculación de claves (kb) confirma que la credencial se comparte con el mismo dispositivo al que se emitió originalmente, lo que evita que se intercepte o se use en otro dispositivo.

La validación en el servidor debe lograr lo siguiente:

  • Verifica la entidad emisora: Asegúrate de que el campo iss (entidad emisora) coincida con https://verifiablecredentials-pa.googleapis.com.
  • Verifica la firma: Comprueba la firma del SD-JWT con las claves públicas (JWK) disponibles en https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.

Para obtener una seguridad completa, asegúrate de validar también el nonce para evitar ataques de repetición.

Si combinas estos pasos, tu servidor puede validar la autenticidad de los datos y la identidad del presentador, lo que garantiza que la credencial no se haya interceptado ni falsificado antes de aprovisionar la cuenta nueva.

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
}

Creación de llaves de acceso

Un paso siguiente opcional, pero muy recomendado, después de aprovisionar una cuenta es crear una llave de acceso para esa cuenta de inmediato. Esto proporciona un método seguro y sin contraseña para que el usuario acceda. Este flujo es idéntico al registro de una llave de acceso estándar.

Compatibilidad con WebView

Para que el flujo funcione en un WebView, los desarrolladores deben implementar un puente de JavaScript (puente de JS) para facilitar la transferencia. Este puente permite que el objeto WebView envíe un indicador a la aplicación nativa, que luego puede realizar la llamada real a la API de Credential Manager.

Consulta también