הטמעה של אימות אימייל

במדריך הזה מוסבר איך להטמיע אחזור של כתובת אימייל מאומתת באמצעות Digital Credentials Verifier API דרך בקשת OpenID for Verifiable Presentations ‏ (OpenID4VP).

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

בקובץ build.gradle של האפליקציה, מוסיפים את יחסי התלות הבאים של Credential Manager:

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

הבקשה מכילה את הפרטים העיקריים הבאים:

  • שאילתת DCQL: השדה dcql_query מציין את סוג פרטי הכניסה ואת ההצהרות המבוקשות (email_verified). אפשר לבקש הצהרות אחרות כדי לקבוע את רמת האימות. הנה כמה דוגמאות לטענות אפשריות:
    • ‫email_verified: בתגובה, זהו ערך בוליאני שמציין אם כתובת האימייל מאומתת.
    • ‫hd (דומיין מתארח): בתגובה, השדה הזה ריק.
  • ‫nonce: ערך אקראי ייחודי ומאובטח מבחינה קריפטוגרפית שנוצר לכל בקשה. זה קריטי לאבטחה, כי זה מונע התקפות שליחה מחדש.
  • ‫UserInfoCredential: הערך הזה מציין סוג ספציפי של פרטי כניסה דיגיטליים שמכילים מאפייני משתמש. חשוב לכלול את זה בבקשה כדי להבדיל בין תרחיש השימוש של אימות האימייל.

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

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

מציגים למשתמש את הבקשה באמצעות ממשק המשתמש המובנה של Credential Manager.

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

טיפול בתגובה

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

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

מכיוון שכתובת האימייל שאוחזרה מאומתת באופן קריפטוגרפי, אתם יכולים לדלג על שלב האימות של כתובת האימייל באמצעות OTP. כך תוכלו לצמצם באופן משמעותי את החיכוך בתהליך ההרשמה, ואולי גם להגדיל את שיעור ההמרה. הדרך הטובה ביותר לטפל בתהליך הזה היא בשרת שלכם. הלקוח שולח את התגובה הגולמית (שמכילה את ה-vp_token) ואת המספר החד-פעמי המקורי לנקודת קצה חדשה בשרת.

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

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

עוד באותו הקשר