Реализуйте проверку электронной почты с помощью API цифровых учетных данных.

В этом руководстве описывается, как реализовать получение подтвержденных адресов электронной почты с помощью API Digital Credentials Verifier через запрос OpenID for Verifiable Presentations (OpenID4VP) .

Добавить зависимости

В файл build.gradle вашего приложения добавьте следующие зависимости для Credential Manager:

Котлин

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

Круто

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

Инициализация диспетчера учетных данных

Используйте контекст вашего приложения или действия для создания объекта CredentialManager .

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

Сформируйте запрос на получение цифровых учетных данных.

Для запроса подтвержденного адреса электронной почты создайте запрос GetCredentialRequest , содержащий параметр GetDigitalCredentialOption . Этот параметр требует строку requestJson отформатированную как запрос OpenID для проверяемых презентаций (OpenID4VP).

Запрос OpenID4VP в формате JSON должен соответствовать определённой структуре. Существующие провайдеры поддерживают структуру JSON с внешней оболочкой "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))

Запрос содержит следующую ключевую информацию:

  • DCQL-запрос : Параметр dcql_query указывает тип учетных данных и запрашиваемые утверждения ( email_verified ). Вы можете запросить и другие утверждения для определения уровня проверки. Вот несколько возможных утверждений:

    • email_verified : В ответе это логическое значение, указывающее, подтвержден ли адрес электронной почты.
    • hd (хостинг домена): В ответе это поле пустое.
  • Если адрес электронной почты не является @gmail.com, Google подтвердил его при создании учетной записи Google, но подтверждение актуальности отсутствует. Поэтому для адресов электронной почты, не относящихся к Google, следует рассмотреть возможность дополнительной проверки, например, с помощью одноразового пароля (OTP), для подтверждения пользователя. Чтобы понять схему учетных данных и конкретные правила проверки таких полей, как email_verified , обратитесь к руководствам Google Identity .

  • nonce : Для каждого запроса генерируется уникальное, криптографически защищенное случайное значение. Это критически важно для безопасности, поскольку предотвращает атаки повторного воспроизведения.

  • UserInfoCredential : Это значение указывает на конкретный тип цифровых учетных данных, содержащих атрибуты пользователя. Включение этого параметра в запрос имеет решающее значение для различения сценария использования, связанного с подтверждением электронной почты.

Далее, оберните JSON-данные openId4vpRequest в GetDigitalCredentialOption , создайте объект GetCredentialRequest и вызовите getCredential() .

Предъявите запрос пользователю.

Отобразите запрос пользователю, используя встроенный пользовательский интерфейс диспетчера учетных данных.

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
}

Проанализируйте ответ на стороне клиента.

После получения ответа можно выполнить предварительный анализ на стороне клиента. Это полезно для немедленного обновления пользовательского интерфейса, например, для отображения имени пользователя.

Приведенный ниже код извлекает необработанный JWT-токен выборочного раскрытия информации (SD-JWT) и использует вспомогательную функцию для декодирования его утверждений.

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

Обработайте ответ

API диспетчера учетных данных вернет ответ DigitalCredential .

Ниже приведён пример того, как выглядит исходный responseJsonString , и как выглядят утверждения после разбора внутреннего SD-JWT, в котором вы получаете дополнительные метаданные, а также подтверждённый адрес электронной почты:

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

Серверная проверка данных при создании учетной записи

Поскольку полученный адрес электронной почты криптографически подтвержден, вы можете пропустить этап подтверждения OTP по электронной почте, что значительно снизит сложности регистрации и потенциально повысит конверсию. Этот процесс лучше всего обрабатывать на вашем сервере. Клиент отправляет необработанный ответ (содержащий vp_token ) и исходный nonce на новую конечную точку сервера.

Для проверки ваше приложение должно отправить полный responseJsonString на ваш сервер для криптографической проверки перед созданием учетной записи или входом пользователя в систему.

Цифровые учетные данные обеспечивают два важных уровня проверки вашего сервера:

  • Подлинность данных : Проверка URL-адреса эмитента ( iss ) и подписи SD-JWT доказывает, что данные были выпущены доверенным центром.
  • Идентификация презентера : Проверка поля cnf и подписи привязки ключа ( kb ) подтверждает, что учетные данные используются тем же устройством, которому они были первоначально выданы, что предотвращает их перехват или использование на другом устройстве.

Проверка на сервере должна обеспечивать следующее:

  • Проверка эмитента : Убедитесь, что поле iss (эмитент) соответствует данным https://verifiablecredentials-pa.googleapis.com .
  • Проверка подписи : Проверьте подпись SD-JWT, используя открытые ключи (JWK), доступные по адресу https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.

Для обеспечения полной безопасности обязательно проверяйте также значение nonce , чтобы предотвратить атаки повторного воспроизведения.

Сочетая эти шаги, ваш сервер может проверить как подлинность данных, так и личность отправителя, гарантируя, что учетные данные не были перехвачены или подделаны до создания новой учетной записи.

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
}

Создание пароля

После создания учетной записи следующим, необязательным, но настоятельно рекомендуемым шагом является немедленное создание пароля для этой учетной записи. Это обеспечивает безопасный способ входа пользователя без пароля. Этот процесс идентичен стандартной регистрации пароля.

Поддержка WebView

Для корректной работы процесса в WebView разработчикам следует реализовать JavaScript-мост (JS Bridge) для облегчения передачи управления. Этот мост позволяет WebView передавать сигнал нативному приложению, которое затем может выполнить фактический вызов API диспетчера учетных данных.

См. также