Triển khai quy trình xác minh email bằng Digital Credentials API

Hướng dẫn này mô tả cách triển khai quy trình truy xuất email đã xác minh bằng API Trình xác minh thông tin xác thực kỹ thuật số thông qua yêu cầu OpenID cho Bản trình bày có thể xác minh (OpenID4VP).

Thêm phần phụ thuộc

Trong tệp build.gradle của ứng dụng, hãy thêm các phần phụ thuộc sau cho Trình quản lý thông tin xác thực:

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

Khởi chạy Trình quản lý thông tin xác thực

Sử dụng ngữ cảnh ứng dụng hoặc hoạt động để tạo đối tượng CredentialManager.

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

Tạo yêu cầu về Thông tin xác thực kỹ thuật số

Để yêu cầu một email đã xác minh, hãy tạo GetCredentialRequest chứa a GetDigitalCredentialOption. Tuỳ chọn này yêu cầu chuỗi requestJson được định dạng dưới dạng yêu cầu OpenID cho Bản trình bày có thể xác minh (OpenID4VP).

JSON yêu cầu OpenID4VP phải tuân theo một cấu trúc cụ thể. Các nhà cung cấp hiện tại hỗ trợ cấu trúc JSON với trình bao bọc "digital": {"requests": [...]} bên ngoài.

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

Yêu cầu chứa thông tin chính sau:

  • Truy vấn DCQL: dcql_query chỉ định loại thông tin xác thực và các khai báo đang được yêu cầu (email_verified). Bạn có thể yêu cầu các khai báo khác để xác định mức xác minh. Dưới đây là một số khai báo có thể có:

    • email_verified: Trong phản hồi, đây là một giá trị boolean cho biết email có được xác minh hay không.
    • hd (tên miền được lưu trữ): Trong phản hồi, trường này trống.
  • Nếu email không phải là @gmail.com, thì Google đã xác minh email này khi Tài khoản Google được tạo, nhưng không có khai báo về độ mới. Do đó, đối với các email không phải của Google, bạn nên cân nhắc thêm một thử thách, chẳng hạn như mã OTP, để xác minh người dùng. Để hiểu rõ về lược đồ của thông tin xác thực và các quy tắc cụ thể để xác thực các trường như email_verified, hãy tham khảo các hướng dẫn về Google Identity.

  • nonce: Một giá trị ngẫu nhiên duy nhất, được bảo mật bằng mật mã sẽ được tạo cho mỗi yêu cầu. Điều này rất quan trọng đối với tính bảo mật, vì nó ngăn chặn các cuộc tấn công phát lại.

  • UserInfoCredential: Giá trị này ngụ ý một loại thông tin xác thực kỹ thuật số cụ thể chứa các thuộc tính của người dùng. Việc đưa thông tin này vào yêu cầu là rất quan trọng để phân biệt trường hợp sử dụng xác minh email.

Tiếp theo, hãy gói JSON openId4vpRequest trong GetDigitalCredentialOption, tạo GetCredentialRequest và gọi getCredential().

Trình bày yêu cầu cho người dùng

Trình bày yêu cầu cho người dùng bằng giao diện người dùng tích hợp của Trình quản lý thông tin xác thực.

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
}

Phân tích cú pháp phản hồi trên ứng dụng

Sau khi nhận được phản hồi, bạn có thể thực hiện phân tích cú pháp sơ bộ trên ứng dụng. Điều này hữu ích để cập nhật ngay giao diện người dùng, chẳng hạn như bằng cách hiển thị tên của người dùng.

Mã sau đây trích xuất JWT Tiết lộ có chọn lọc (SD-JWT) thô và sử dụng một trình trợ giúp để giải mã các khai báo của 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"))
)

Xử lý phản hồi

Credential Manager API sẽ trả về phản hồi DigitalCredential.

Sau đây là ví dụ về giao diện của responseJsonString thô và giao diện của các khai báo sau khi phân tích cú pháp SD-JWT bên trong, nơi bạn cũng nhận được thêm siêu dữ liệu cùng với email đã xác minh:

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

Xác thực phía máy chủ để tạo tài khoản

Vì email đã truy xuất được xác minh bằng mật mã, nên bạn có thể bỏ qua bước xác minh OTP qua email, giảm đáng kể sự phiền hà khi đăng ký và có khả năng tăng tỷ lệ chuyển đổi. Bạn nên xử lý quy trình này trên máy chủ. Ứng dụng gửi phản hồi thô (chứa vp_token) và nonce ban đầu đến một điểm cuối máy chủ mới.

Để xác minh, ứng dụng của bạn phải gửi toàn bộ responseJsonString đến máy chủ để xác thực mật mã trước khi tạo tài khoản hoặc đăng nhập cho người dùng.

Thông tin xác thực kỹ thuật số cung cấp 2 cấp độ xác minh quan trọng cho máy chủ:

  • Tính xác thực của dữ liệu: Việc xác minh URL của tổ chức phát hành (iss) và chữ ký SD-JWT chứng minh rằng một cơ quan đáng tin cậy đã phát hành dữ liệu này.
  • Danh tính của người trình bày: Việc xác minh trường cnf và chữ ký Liên kết khoá (kb) xác nhận rằng thông tin xác thực đang được chia sẻ bởi cùng một thiết bị mà thông tin xác thực đó được phát hành ban đầu, ngăn chặn việc thông tin xác thực bị chặn hoặc sử dụng trên một thiết bị khác.

Quy trình xác thực trên máy chủ phải đạt được những điều sau:

  • Xác minh tổ chức phát hành: Đảm bảo trường iss (tổ chức phát hành) khớp với https://verifiablecredentials-pa.googleapis.com.
  • Xác minh chữ ký: Kiểm tra chữ ký của SD-JWT bằng các khoá công khai (JWK) có tại https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.

Để đảm bảo an toàn đầy đủ, hãy nhớ xác thực nonce để ngăn chặn các cuộc tấn công phát lại.

Bằng cách kết hợp các bước này, máy chủ có thể xác thực cả tính xác thực của dữ liệu và danh tính của người trình bày, đảm bảo rằng thông tin xác thực không bị chặn hoặc giả mạo trước khi cung cấp tài khoản mới.

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
}

Tạo khoá truy cập

Bước tiếp theo không bắt buộc nhưng bạn nên thực hiện sau khi cung cấp tài khoản là ngay lập tức tạo khoá truy cập cho tài khoản đó. Điều này cung cấp một phương thức an toàn, không cần mật khẩu để người dùng đăng nhập. Quy trình này giống hệt với quy trình đăng ký khoá truy cập tiêu chuẩn.

Hỗ trợ WebView

Để quy trình hoạt động trên WebView, nhà phát triển nên triển khai cầu nối JavaScript (Cầu nối JS) để tạo điều kiện chuyển giao. Cầu nối này cho phép WebView báo hiệu ứng dụng gốc, sau đó có thể thực hiện lệnh gọi thực tế đến API Trình quản lý thông tin xác thực.

Xem thêm