Implémenter la validation des adresses e-mail avec l'API Digital Credentials

Présentation

Ajouter des dépendances

Dans le fichier build.gradle de votre application, ajoutez les dépendances suivantes pour Credential Manager :

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"
}

Initialiser Credential Manager

Utilisez le contexte de votre application ou de votre activité pour créer un objet CredentialManager.

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

Créer la requête Digital Credential

Pour demander un e-mail de validation, créez un GetCredentialRequest contenant un GetDigitalCredentialOption. Cette option nécessite une chaîne requestJson au format d'une requête OpenID pour les présentations vérifiables (OpenID4VP).

Le fichier JSON de la requête OpenID4VP doit suivre une structure spécifique. Les fournisseurs actuels sont compatibles avec une structure JSON avec un wrapper "digital": {"requests": [...]} externe.

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

Ensuite, encapsulez le fichier JSON openId4vpRequest dans un GetDigitalCredentialOption, créez un GetCredentialRequest et appelez getCredential().

Présenter la demande à l'utilisateur

Présentez la requête à l'utilisateur à l'aide de l'interface utilisateur intégrée de Credential Manager.

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
    }
}

Analyser la réponse sur le client

Après avoir reçu la réponse, vous pouvez effectuer une analyse préliminaire sur le client. Cela est utile pour mettre à jour immédiatement l'UI, par exemple en affichant le nom de l'utilisateur.

Le code suivant extrait le jeton JWT de divulgation sélective (SD-JWT) brut et utilise un outil d'aide pour décoder ses revendications.

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

Gérer la réponse

L'API Credential Manager renvoie une réponse DigitalCredential.

Voici un exemple de ce à quoi ressemble le responseJsonString brut et de ce à quoi ressemblent les revendications après l'analyse du SD-JWT interne, où vous obtenez également des métadonnées supplémentaires ainsi que l'adresse e-mail validée :

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

Validation côté serveur pour la création de compte

Pour la validation, votre application doit envoyer l'intégralité de responseJsonString à votre serveur pour une validation cryptographique avant de créer un compte ou de connecter l'utilisateur.

  • Authenticité des données : la vérification de l'URL de l'émetteur (iss) et de la signature SD-JWT prouve qu'une autorité de confiance a émis ces données.

La validation sur le serveur doit permettre d'atteindre les objectifs suivants :

  • Vérifier l'émetteur : assurez-vous que le champ iss (émetteur) correspond à https://verifiablecredentials-pa.googleapis.com.
  • Vérifier la signature : vérifiez la signature du SD-JWT à l'aide des clés publiques (JWK) disponibles sur https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.

Pour une sécurité totale, veillez à valider également le nonce pour éviter les attaques par relecture.

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
}

Après avoir provisionné un compte, nous vous recommandons vivement de créer immédiatement une clé d'accès pour ce compte. Cela permet à l'utilisateur de se connecter de manière sécurisée et sans mot de passe. Ce flux est identique à l'enregistrement d'une clé d'accès standard.

Pour que le flux fonctionne sur un WebView, les développeurs doivent implémenter un pont JavaScript (pont JS) afin de faciliter le transfert. Ce pont permet à l'objet WebView de signaler l'application native, qui peut ensuite effectuer l'appel réel à l'API Credential Manager.