Wdrażanie weryfikacji adresu e-mail

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

Dodawanie zależności

W pliku build.gradle aplikacji dodaj te zależności dla Menedżera danych logowania:

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

Inicjowanie Menedżera danych logowania

Użyj kontekstu aplikacji lub 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 cyfrowego dokumentu tożsamości

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

Plik JSON żądania OpenID4VP musi mieć określoną strukturę. Obecni dostawcy obsługują strukturę JSON z zewnętrznym kodem „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))

Żądanie zawiera te kluczowe informacje:

  • Zapytanie DCQL: dcql_query określa typ danych uwierzytelniających i żądane uprawnienia (email_verified). Możesz poprosić o inne uprawnienia, aby określić poziom weryfikacji. Oto kilka możliwych twierdzeń:
    • email_verified: w odpowiedzi jest to wartość logiczna, która wskazuje, czy adres e-mail został zweryfikowany.
    • hd (domena hostowana): w odpowiedzi to pole jest puste.
  • nonce: dla każdego żądania generowana jest unikalna, kryptograficznie bezpieczna wartość losowa. Ma to kluczowe znaczenie dla bezpieczeństwa, ponieważ zapobiega atakom typu replay.
  • UserInfoCredential: ta wartość oznacza określony typ cyfrowego dokumentu tożsamości, który zawiera atrybuty użytkownika. Uwzględnienie tego w żądaniu jest kluczowe, aby odróżnić przypadek użycia potwierdzania adresu e-mail.

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

Przedstawienie prośby użytkownikowi

Wyświetl użytkownikowi prośbę, korzystając z 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 kliencie

Po otrzymaniu odpowiedzi możesz przeprowadzić wstępną analizę po stronie klienta. Jest to przydatne, gdy chcesz natychmiast zaktualizować interfejs, np. wyświetlić nazwę użytkownika.

Poniższy kod wyodrębnia nieprzetworzony token JWT z ujawnianiem selektywnym (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 nieprzetworzonego tokena responseJsonString oraz uprawnień po przeanalizowaniu wewnętrznego tokena SD-JWT, w którym oprócz zweryfikowanego adresu e-mail otrzymujesz 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ą hasła jednorazowego, co znacznie ułatwia rejestrację i może zwiększyć liczbę konwersji. Najlepiej przeprowadzić ten proces na serwerze. Klient wysyła nieprzetworzoną odpowiedź (zawierającą vp_token) i pierwotną liczbę jednorazową do nowego punktu końcowego serwera.

W celu weryfikacji aplikacja musi wysłać pełny obiekt responseJsonString na serwer, aby przeprowadzić weryfikację kryptograficzną, zanim utworzy konto lub zaloguje użytkownika.

Cyfrowe dane uwierzytelniające zapewniają 2 ważne poziomy weryfikacji serwera:

  • Autentyczność danych: weryfikacja adresu URL wydawcy (iss) i podpisu SD-JWT potwierdza, że te dane zostały wydane przez zaufany organ.
  • Tożsamość osoby okazującej dokument: weryfikacja pola cnf i podpisu powiązania klucza (kb) potwierdza, że dokument jest udostępniany przez to samo urządzenie, na które został pierwotnie wydany, co zapobiega jego przechwyceniu lub użyciu na innym urządzeniu.

Weryfikacja na serwerze musi spełniać te wymagania:

  • Zweryfikuj wydawcę: upewnij się, że pole iss (wydawca) 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, która je przedstawia, co daje pewność, że dokument nie został przechwycony ani sfałszowany przed udostępnieniem nowego konta.

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 zdecydowanie zalecanym kolejnym krokiem po udostępnieniu konta jest natychmiastowe utworzenie klucza dostępu do tego konta. Zapewnia to użytkownikowi bezpieczną metodę logowania bez hasła. Ten proces jest identyczny ze standardową rejestracją klucza dostępu.

Obsługa WebView

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

Zobacz też