Menerapkan verifikasi email dengan Digital Credentials API

Ringkasan

Menambahkan dependensi

Di file build.gradle aplikasi Anda, tambahkan dependensi berikut untuk 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"
}

Melakukan inisialisasi Credential Manager

Gunakan konteks aplikasi atau aktivitas Anda untuk membuat objek CredentialManager.

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

Membuat permintaan Kredensial Digital

Untuk meminta email terverifikasi, buat GetCredentialRequest yang berisi GetDigitalCredentialOption. Opsi ini memerlukan string requestJson yang diformat sebagai permintaan OpenID for Verifiable Presentations (OpenID4VP).

JSON permintaan OpenID4VP harus mengikuti struktur tertentu. Penyedia saat ini mendukung struktur JSON dengan wrapper "digital": {"requests": [...]} luar.

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

Selanjutnya, gabungkan JSON openId4vpRequest dalam GetDigitalCredentialOption, buat GetCredentialRequest, dan panggil getCredential().

Menampilkan permintaan kepada pengguna

Tampilkan permintaan kepada pengguna, menggunakan UI bawaan Credential Manager.

coroutineScope {
    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
    }
}

Mengurai respons di klien

Setelah menerima respons, Anda dapat melakukan penguraian awal pada klien. Hal ini berguna untuk segera memperbarui UI, misalnya, dengan menampilkan nama pengguna.

Kode berikut mengekstrak Selective Disclosure JWT (SD-JWT) mentah dan menggunakan helper untuk mendekode klaimnya.

// 1. Parse the outer JSON wrapper to get the `vp_token`
val responseData = JSONObject(responseJsonString)
val dataObject = responseData.getJSONObject("data")
val vpToken = dataObject.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"))
)

Menangani respons

Credential Manager API akan menampilkan respons DigitalCredential.

Berikut adalah contoh tampilan responseJsonString mentah, dan tampilan klaim setelah mengurai SD-JWT dalam tempat Anda mendapatkan metadata tambahan beserta email terverifikasi:

/*
// Example of the raw JSON response from credential.credentialJson:
{
  "protocol": "openid4vp-v1-unsigned",
  "data": {
    "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": ""
}
 */

Validasi sisi server untuk pembuatan akun

Untuk verifikasi, aplikasi Anda harus mengirimkan responseJsonString lengkap ke server Anda untuk validasi kriptografis sebelum membuat akun atau membuat pengguna login.

  • Keaslian data: Memverifikasi URL penerbit (iss) dan tanda tangan SD-JWT membuktikan bahwa otoritas tepercaya menerbitkan data ini.

Validasi di server harus mencapai hal berikut:

  • Verifikasi penerbit: Pastikan kolom iss (penerbit) cocok dengan https://verifiablecredentials-pa.googleapis.com.
  • Verifikasi tanda tangan: Periksa tanda tangan SD-JWT menggunakan kunci publik (JWK) yang tersedia di https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.

Untuk keamanan penuh, pastikan Anda juga memvalidasi nonce untuk mencegah serangan 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
}

Langkah berikutnya yang opsional, tetapi sangat direkomendasikan setelah menyediakan akun adalah segera membuat kunci sandi untuk akun tersebut. Tindakan ini memberikan metode login yang aman dan tanpa sandi bagi pengguna. Alur ini sama dengan pendaftaran kunci sandi standar.

Agar alur berfungsi di WebView, developer harus menerapkan jembatan JavaScript (JS Bridge) untuk memfasilitasi penyerahan. Bridge ini memungkinkan objek WebView memberi sinyal ke aplikasi native, yang kemudian dapat melakukan panggilan sebenarnya ke Credential Manager API.