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

Dodawanie zależności

W pliku build.gradle aplikacji dodaj te zależności 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"
}

Inicjowanie Credential Manager

Aby utworzyć obiekt CredentialManager, użyj kontekstu aplikacji lub aktywności.

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

Tworzenie żądania Digital Credential

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

JSON żądania OpenID4VP musi mieć określoną strukturę. Obecni dostawcy obsługują strukturę JSON z zewnętrznym "digital": {"requests": [...]} otoką.

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

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

Przedstawianie żądania użytkownikowi

Przedstaw użytkownikowi żądanie za pomocą wbudowanego interfejsu 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
}

Parsowanie odpowiedzi na kliencie

Po otrzymaniu odpowiedzi możesz przeprowadzić wstępne parsowanie na kliencie. Jest to przydatne do natychmiastowego aktualizowania interfejsu, np. przez wyświetlanie nazwy użytkownika.

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

// 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 DigitalCredential odpowiedź.

Oto przykład surowego ciągu responseJsonString oraz danych po przeanalizowaniu wewnętrznego SD-JWT, w którym oprócz zweryfikowanego adresu e-mail otrzymujesz też 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 na potrzeby tworzenia konta

Aby przeprowadzić weryfikację, przed utworzeniem konta lub zalogowaniem użytkownika aplikacja musi wysłać pełny ciąg responseJsonString do serwera w celu przeprowadzenia weryfikacji kryptograficznej.

  • Autentyczność danych: sprawdzenie adresu URL wystawcy (iss) i podpisu SD-JWT potwierdza, że te dane zostały wydane przez zaufany organ.

Weryfikacja na serwerze musi obejmować te elementy:

  • Sprawdzenie wystawcy: upewnij się, że pole iss (wystawca) jest zgodne z https://verifiablecredentials-pa.googleapis.com.
  • Sprawdzenie podpisu: sprawdź podpis SD-JWT za pomocą kluczy publicznych (JWK) dostępnych pod adresem https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.

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

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
}

Opcjonalnym, ale wysoce zalecanym następnym krokiem po udostępnieniu 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.

Aby proces działał w WebView, deweloperzy powinni zaimplementować mostek JavaScript (JS Bridge), który ułatwi przekazywanie. Ten mostek umożliwia obiektowi WebView sygnalizowanie aplikacji natywnej, która może następnie wykonać rzeczywiste wywołanie interfejsu Credential Manager API.