Wdrażanie weryfikacji adresu e-mail za pomocą interfejsu Digital Credentials API

Z tego przewodnika dowiesz się, jak wdrożyć pobieranie zweryfikowanego adresu e-mail za pomocą interfejsu Digital Credentials Verifier API w ramach żądania OpenID for Verifiable Presentations (OpenID4VP).

Dodawanie zależności

W pliku build.gradle aplikacji dodaj te zależności dla Credential Manager:

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

Inicjowanie Credential Manager API

Użyj aplikacji lub kontekstu aktywności, aby utworzyć obiekt CredentialManager.

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

Tworzenie żądania danych logowania w formie cyfrowej

Aby poprosić o zweryfikowany adres e-mail, utwórz GetCredentialRequest zawierający GetDigitalCredentialOption. Ta opcja wymaga requestJsonciągu tekstowego sformatowanego jako żądanie OpenID for Verifiable Presentations (OpenID4VP).

Żądanie OpenID4VP w formacie JSON musi mieć określoną strukturę. Obecni dostawcy obsługują strukturę JSON z zewnętrznym elementem opakowującym "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))

Żądanie zawiera te kluczowe informacje:

  • Zapytanie DCQL: parametr dcql_query określa typ danych logowania i żądane roszczenia (email_verified). Możesz poprosić o inne roszczenia, aby określić poziom weryfikacji. Oto kilka możliwych roszczeń:

    • email_verified: W odpowiedzi jest to wartość logiczna wskazująca, czy e-mail jest zweryfikowany.
    • hd (domena hostowana): w odpowiedzi to pole jest puste.
  • Jeśli adres e-mail nie jest adresem @gmail.com, Google zweryfikowało go podczas tworzenia konta Google, ale nie ma informacji o jego aktualności. Dlatego w przypadku adresów e-mail innych niż Google warto rozważyć dodatkowy test zabezpieczający, np. hasło jednorazowe, aby zweryfikować użytkownika. Aby poznać schemat dokumentu tożsamości i szczegółowe reguły weryfikacji pól, takich jak email_verified, zapoznaj się z przewodnikami Google Identity.

  • nonce: dla każdego żądania generowana jest niepowtarzalna, bezpieczna kryptograficznie wartość losowa. Ma to kluczowe znaczenie dla bezpieczeństwa, ponieważ zapobiega atakom typu replay.

  • UserInfoCredential: ta wartość oznacza określony typ cyfrowych poświadczeń, które zawierają atrybuty użytkownika. Uwzględnienie tego w prośbie ma kluczowe znaczenie dla odróżnienia przypadku użycia weryfikacji adresu e-mail.

Następnie umieść openId4vpRequest JSON w GetDigitalCredentialOption, utwórz GetCredentialRequest i wywołaj getCredential().

Wyświetlanie prośby użytkownikowi

Wyświetl użytkownikowi prośbę za pomocą wbudowanego interfejsu Menedżera danych logowania.

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
}

Analizowanie odpowiedzi na urządzeniu klienta

Po otrzymaniu odpowiedzi możesz wstępnie przeanalizować ją na urządzeniu klienta. Jest to przydatne, gdy chcesz natychmiast zaktualizować interfejs, np. wyświetlić nazwę użytkownika.

Poniższy kod wyodrębnia surowy token JWT z selektywnym ujawnianiem informacji (SD-JWT) i używa funkcji pomocniczej do dekodowania jego deklaracji.

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

Obsługa odpowiedzi

Interfejs Credential Manager API zwróci odpowiedź DigitalCredential.

Poniżej znajdziesz przykład surowego tokena responseJsonString oraz danych po przeanalizowaniu wewnętrznego tokena SD-JWT, w którym oprócz zweryfikowanego adresu e-mail uzyskasz dodatkowe metadane:

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

Weryfikacja po stronie serwera podczas tworzenia konta

Pobrany adres e-mail jest weryfikowany kryptograficznie, więc możesz pominąć krok weryfikacji adresu e-mail za pomocą kodu OTP. Znacząco zmniejszy to trudności związane z rejestracją i potencjalnie zwiększy liczbę konwersji. Najlepiej przeprowadzić ten proces na serwerze. Klient wysyła nieprzetworzoną odpowiedź (zawierającą vp_token) i pierwotny nonce do nowego punktu końcowego serwera.

Aby przeprowadzić weryfikację, aplikacja musi wysłać pełny ciąg responseJsonString na serwer w celu kryptograficznego sprawdzenia poprawności, zanim utworzy konto lub zaloguje użytkownika.

Cyfrowe poświadczenie zapewnia 2 poziomy weryfikacji serwera:

  • Autentyczność danych: weryfikacja adresu URL wydawcy (iss) i podpisu SD-JWT potwierdza, że dane zostały wydane przez zaufany organ.
  • Tożsamość osoby prezentującej: weryfikacja pola cnf i podpisu Key Binding (kb) potwierdza, że dane logowania są udostępniane przez to samo urządzenie, na które zostały pierwotnie wydane, co uniemożliwia ich przechwycenie lub użycie na innym urządzeniu.

Weryfikacja na serwerze musi spełniać te warunki:

  • Weryfikacja wystawcy: sprawdź, czy pole iss (wystawca) jest zgodne z https://verifiablecredentials-pa.googleapis.com.
  • Weryfikacja podpisu: sprawdź podpis SD-JWT za pomocą kluczy publicznych (JWK) dostępnych na stronie https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.

Aby zapewnić pełne bezpieczeństwo, sprawdź też nonce, aby zapobiec atakom typu replay.

Dzięki połączeniu tych kroków serwer może zweryfikować zarówno autentyczność danych, jak i tożsamość osoby prezentującej, co gwarantuje, że zanim nowe konto zostanie udostępnione, dane logowania nie zostaną przechwycone ani sfałszowane.

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
}

Tworzenie klucza dostępu

Opcjonalnym, ale wysoce zalecanym kolejnym krokiem po utworzeniu konta jest natychmiastowe utworzenie klucza dostępu do tego konta. Zapewnia to bezpieczną metodę logowania bez hasła. Ten proces jest identyczny ze standardową rejestracją klucza dostępu.

Obsługa WebView

Aby proces działał w WebView, deweloperzy powinni wdrożyć most JavaScript (JS Bridge), który ułatwi przekazywanie danych. Ten pomost umożliwia widokowi WebView wysyłanie sygnałów do aplikacji natywnej, która może następnie wykonać rzeczywiste wywołanie interfejsu Credential Manager API.

Zobacz również