E‑Mail-Bestätigung mit der Digital Credentials API implementieren

In diesem Leitfaden wird beschrieben, wie Sie den Abruf bestätigter E‑Mail-Adressen mithilfe der Digital Credentials Verifier API über eine OpenID for Verifiable Presentations (OpenID4VP)-Anfrage implementieren.

Abhängigkeiten hinzufügen

Fügen Sie der Datei build.gradle Ihrer App die folgenden Abhängigkeiten für Credential Manager hinzu:

Kotlin

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

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha01"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha01"
}

Credential Manager initialisieren

Erstellen Sie ein CredentialManager-Objekt mit Ihrer App oder Ihrem Aktivitätskontext.

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

Anfrage für digitale Anmeldedaten erstellen

Wenn Sie eine bestätigte E‑Mail-Adresse anfordern möchten, erstellen Sie eine GetCredentialRequest mit einer GetDigitalCredentialOption. Für diese Option ist ein requestJson-String erforderlich, der als OpenID4VP-Anfrage (OpenID for Verifiable Presentations) formatiert ist.

Das OpenID4VP-Anfrage-JSON muss einer bestimmten Struktur folgen. Die aktuellen Anbieter unterstützen eine JSON-Struktur mit einem äußeren "digital": {"requests": [...]}-Wrapper.

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

Der Antrag enthält die folgenden wichtigen Informationen:

  • DCQL-Abfrage: Mit dcql_query wird der Anmeldedatentyp und die angeforderten Anforderungen (email_verified) angegeben. Sie können andere Anforderungen anfordern, um den Grad der Überprüfung zu bestimmen. Hier einige Beispiele für mögliche Ansprüche:

    • email_verified: In der Antwort ist dies ein boolescher Wert, der angibt, ob die E-Mail-Adresse bestätigt wurde.
    • hd (gehostete Domain): In der Antwort ist dieses Feld leer.
  • Wenn die E‑Mail-Adresse nicht auf @gmail.com endet, hat Google sie bei der Erstellung des Google-Kontos bestätigt, es gibt aber keinen Anspruch auf Aktualität. Daher sollten Sie für E-Mail-Adressen, die nicht von Google stammen, eine zusätzliche Bestätigung in Betracht ziehen, z. B. einen Einmalcode, um den Nutzer zu bestätigen. Informationen zum Schema der Anmeldedaten und zu den spezifischen Regeln für die Validierung von Feldern wie email_verified finden Sie in den Google Identity-Leitfäden.

  • Nonce: Für jede Anfrage wird ein eindeutiger, kryptografisch sicherer Zufallswert generiert. Dies ist für die Sicherheit von entscheidender Bedeutung, da es Wiederholungsangriffe verhindert.

  • UserInfoCredential: Dieser Wert impliziert einen bestimmten Typ von digitalem Anmeldedatum, das Nutzerattribute enthält. Die Einbeziehung in die Anfrage ist entscheidend, um den Anwendungsfall der E‑Mail-Bestätigung zu unterscheiden.

Umschließen Sie als Nächstes das openId4vpRequest-JSON mit einem GetDigitalCredentialOption, erstellen Sie ein GetCredentialRequest und rufen Sie getCredential() auf.

Anfrage dem Nutzer präsentieren

Präsentieren Sie dem Nutzer die Anfrage über die integrierte Benutzeroberfläche des 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
}

Antwort auf dem Client parsen

Nachdem Sie die Antwort erhalten haben, können Sie eine vorläufige Analyse auf dem Client durchführen. Dies ist nützlich, um die Benutzeroberfläche sofort zu aktualisieren, z. B. um den Namen des Nutzers anzuzeigen.

Im folgenden Code wird das Roh-SD-JWT extrahiert und ein Helfer verwendet, um die zugehörigen Behauptungen zu decodieren.

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

Antwort verarbeiten

Die Credential Manager API gibt eine DigitalCredential-Antwort zurück.

Das Folgende ist ein Beispiel dafür, wie das Roh-responseJsonString aussieht und wie die Ansprüche nach dem Parsen des inneren SD-JWT aussehen, wobei Sie neben der bestätigten E-Mail-Adresse auch zusätzliche Metadaten erhalten:

/*
// 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": "[email protected]",
  "email_verified": true,
  "given_name": "Jane",
  "family_name": "Doe",
  "name": "Jane Doe",
  "picture": "http://example.com/janedoe/me.jpg",
  "hd": ""
}
 */

Serverseitige Validierung für die Kontoerstellung

Da die abgerufene E-Mail-Adresse kryptografisch überprüft wird, können Sie den Schritt zur Bestätigung der E-Mail-Adresse per Einmalpasswort überspringen. So wird die Registrierung deutlich vereinfacht und die Conversion-Rate kann steigen. Dieser Vorgang sollte am besten auf Ihrem Server ausgeführt werden. Der Client sendet die Rohantwort (mit dem vp_token) und die ursprüngliche Nonce an einen neuen Serverendpunkt.

Zur Überprüfung muss Ihre Anwendung das vollständige responseJsonString an Ihren Server zur kryptografischen Validierung senden, bevor ein Konto erstellt oder der Nutzer angemeldet wird.

Die digitalen Anmeldedaten bieten zwei wichtige Überprüfungsebenen für Ihren Server:

  • Authentizität der Daten: Durch die Überprüfung der URL des Ausstellers (iss) und der Signatur SD-JWT wird nachgewiesen, dass diese Daten von einer vertrauenswürdigen Behörde ausgestellt wurden.
  • Identität des Präsentierenden: Durch die Überprüfung des Felds cnf und der Signatur für die Schlüsselbindung (kb) wird bestätigt, dass der Berechtigungsnachweis von demselben Gerät geteilt wird, für das er ursprünglich ausgestellt wurde. So wird verhindert, dass er abgefangen oder auf einem anderen Gerät verwendet wird.

Die Validierung auf dem Server muss Folgendes leisten:

  • Aussteller überprüfen: Achten Sie darauf, dass das Feld iss (Aussteller) mit https://verifiablecredentials-pa.googleapis.com übereinstimmt.
  • Signatur prüfen: Prüfen Sie die Signatur des SD-JWT mit den öffentlichen Schlüsseln (JWKs), die unter https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks verfügbar sind.

Für maximale Sicherheit sollten Sie auch die nonce validieren, um Replay-Angriffe zu verhindern.

Durch die Kombination dieser Schritte kann Ihr Server sowohl die Authentizität der Daten als auch die Identität des Präsentators validieren. So wird sichergestellt, dass die Anmeldedaten nicht abgefangen oder gefälscht wurden, bevor das neue Konto bereitgestellt wird.

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
}

Passkey erstellen

Ein optionaler, aber sehr empfehlenswerter nächster Schritt nach der Bereitstellung eines Kontos ist, sofort einen Passkey für dieses Konto zu erstellen. So kann sich der Nutzer sicher und ohne Passwort anmelden. Dieser Ablauf ist mit einer standardmäßigen Passkey-Registrierung identisch.

WebView-Unterstützung

Damit der Ablauf in einer WebView funktioniert, sollten Entwickler eine JavaScript-Bridge (JS-Bridge) implementieren, um die Übergabe zu ermöglichen. Über diese Bridge kann die WebView die systemeigene App benachrichtigen, die dann den eigentlichen Aufruf der Credential Manager API ausführen kann.

Siehe auch