پیاده‌سازی درستی‌سنجی ایمیل

این راهنما نحوه پیاده‌سازی بازیابی ایمیل درستی‌سنجی‌شده را بااستفاده از Digital Credentials Verifier API ازطریق درخواست OpenID for Verifiable Presentations (OpenID4VP) شرح می‌دهد.

افزودن وابستگی‌ها

در فایل build.gradle برنامه‌تان، وابستگی‌های زیر را برای «مدیر اطلاعات کاربری» اضافه کنید:

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 برای «ارائه‌های قابل‌تأیید» (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: این مقدار بر نوع خاصی از اطلاعات کاربری دیجیتال دلالت دارد که حاوی مشخصه‌های کاربر است. گنجاندن این مورد در درخواست برای تشخیص مورد استفاده درستی‌سنجی ایمیل بسیار مهم است.

سپس، openId4vpRequest JSON را در 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
}

تجزیه پاسخ در کارخواه

پس‌از دریافت پاسخ، می‌توانید تجزیه مقدماتی را در کارخواه انجام دهید. این کار برای به‌روزرسانی فوری واسط کاربر مفید است، برای مثال، با نشان دادن نام کاربر.

کد زیر 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": ""
}
 */

اعتبارسنجی سمت سرور برای ایجاد حساب

ازآنجایی‌که ایمیل بازیابی‌شده به‌صورت رمزنگاری‌شده درستی‌سنجی می‌شود، می‌توانید مرحله درستی‌سنجی ایمیل با گذرواژه یک‌بارمصرف را حذف کنید، که این کار اصطکاک ثبت‌نام را به‌طور قابل‌توجهی کاهش می‌دهد و احتمالاً نرخ تبدیل را افزایش می‌دهد. این فرایند بهتر است در سرور شما انجام شود. کارخواه پاسخ خام (حاوی vp_token) و عدد یک‌باراستفاده اصلی را به نقطه پایانی سرور جدیدی ارسال می‌کند.

برای درستی‌سنجی، برنامه‌تان باید responseJsonString کامل را برای اعتبارسنجی رمزنگاری به سرورتان ارسال کند و سپس حساب ایجاد کند یا کاربر را به سیستم وارد کند.

اعتبارنامه دیجیتال دو سطح مهم درستی‌سنجی برای سرور شما ارائه می‌دهد:

  • اصالت داده‌ها: درستی‌سنجی نشانی وب صادرکننده (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 کار کند، توسعه‌دهندگان باید پل جاوا اسکریپت (JS Bridge) را برای تسهیل انتقال پیاده‌سازی کنند. این پل به شیء WebView اجازه می‌دهد به برنامه به زبان اصلی دستگاه سیگنال دهد، که سپس می‌تواند فراخوانی واقعی را به Credential Manager API انجام دهد.

همچنین مراجعه کنید به