הטמעה של אימות כתובת אימייל באמצעות Digital Credentials API

סקירה כללית

הוספת יחסי תלות

בקובץ build.gradle של האפליקציה, מוסיפים את יחסי התלות הבאים של 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"
}

הפעלה של Credential Manager

משתמשים בהקשר של האפליקציה או הפעילות כדי ליצור אובייקט CredentialManager.

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

יצירת הבקשה לאישור הדיגיטלי

כדי לבקש אימייל מאומת, יוצרים GetCredentialRequest שמכיל GetDigitalCredentialOption. כדי להשתמש באפשרות הזו, צריך מחרוזת requestJson בפורמט של בקשת OpenID for Verifiable Presentations ‏(OpenID4VP).

בקשת ה-JSON של OpenID4VP צריכה להיות במבנה ספציפי. הספקים הנוכחיים תומכים במבנה 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))

אחר כך עוטפים את ה-JSON של openId4vpRequest ב-GetDigitalCredentialOption, יוצרים GetCredentialRequest וקוראים ל-getCredential().

הצגת הבקשה למשתמש

מציגים למשתמש את הבקשה באמצעות ממשק המשתמש המובנה של 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
    }
}

ניתוח התגובה בצד הלקוח

אחרי שמקבלים את התשובה, אפשר לבצע ניתוח ראשוני בצד הלקוח. זה שימושי לעדכון מיידי של ממשק המשתמש, למשל, על ידי הצגת שם המשתמש.

הקוד הבא מחלץ את Selective Disclosure JWT (SD-JWT) הגולמי ומשתמש בכלי עזר כדי לפענח את ההצהרות שלו.

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

טיפול בתגובה

‫Credential Manager API יחזיר תגובה מסוג DigitalCredential.

הדוגמה הבאה מראה איך נראה ה-responseJsonString הגולמי, ואיך נראות ההצהרות אחרי ניתוח ה-SD-JWT הפנימי, שבו מקבלים גם מטא-נתונים נוספים וגם כתובת אימייל מאומתת:

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

אימות בצד השרת ליצירת חשבון

כדי לבצע אימות, האפליקציה צריכה לשלוח את ה-responseJsonString המלא לשרת שלכם לצורך אימות קריפטוגרפי לפני יצירת חשבון או התחברות של המשתמש.

  • אותנטיות הנתונים: אימות כתובת ה-URL של המנפיק (iss) והחתימה של SD-JWT מוכיח שרשות מהימנה הנפיקה את הנתונים האלה.

האימות בשרת צריך:

  • אימות המנפיק: מוודאים שהשדה iss (מנפיק) תואם ל-https://verifiablecredentials-pa.googleapis.com.
  • אימות החתימה: בודקים את החתימה של ה-SD-JWT באמצעות המפתחות הציבוריים (JWK) שזמינים בכתובת https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks.

כדי להבטיח אבטחה מלאה, חשוב לוודא שאתם מאמתים גם את nonce כדי למנוע התקפות מסוג 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
}

אחרי שמקצים חשבון, מומלץ מאוד ליצור מיד מפתח גישה לחשבון הזה. כך המשתמשים יכולים להיכנס לחשבון בצורה מאובטחת בלי סיסמה. התהליך הזה זהה לתהליך הרגיל של רישום מפתח גישה.

כדי שהתהליך יעבוד ב-WebView, המפתחים צריכים להטמיע גשר JavaScript (גשר JS) שיאפשר את ההעברה. הגשר הזה מאפשר לאובייקט WebView לשלוח אות לאפליקציית הנייטיב, שיכולה לבצע את הקריאה בפועל ל-Credential Manager API.