پیاده‌سازی تأیید ایمیل با API اعتبارنامه‌های دیجیتال

این راهنما نحوه پیاده‌سازی بازیابی ایمیل تأیید شده را با استفاده از API تأییدکننده اعتبارنامه‌های دیجیتال از طریق درخواست OpenID برای ارائه‌های قابل تأیید (OpenID4VP) شرح می‌دهد.

وابستگی‌ها را اضافه کنید

در فایل build.gradle برنامه خود، وابستگی‌های زیر را برای Credential Manager اضافه کنید:

کاتلین

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha01")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha01")
}

شیار

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha01"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha01"
}

مقداردهی اولیه مدیریت اعتبارنامه

از زمینه برنامه یا فعالیت خود برای ایجاد یک شیء 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 (دامنه میزبانی شده): در پاسخ، این قسمت خالی است.
  • اگر ایمیل non-@gmail.com باشد، گوگل این ایمیل را هنگام ایجاد حساب گوگل تأیید کرده است، اما هیچ ادعای تازگی وجود ندارد. بنابراین، برای ایمیل‌های غیر گوگل، باید یک چالش اضافی، مانند یک رمز یکبار مصرف (OTP)، برای تأیید کاربر در نظر بگیرید. برای درک طرحواره اعتبارنامه و قوانین خاص برای اعتبارسنجی فیلدهایی مانند email_verified ، به راهنماهای Google Identity مراجعه کنید.

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

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

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

کد زیر 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 و Claims پس از تجزیه 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 ) و nonce اصلی را به یک نقطه پایانی سرور جدید ارسال می‌کند.

برای تأیید، برنامه شما باید قبل از ایجاد حساب کاربری یا ورود کاربر، فایل کامل responseJsonString برای اعتبارسنجی رمزنگاری به سرور شما ارسال کند.

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

  • صحت داده‌ها : تأیید URL صادرکننده ( iss ) و امضای SD-JWT ثابت می‌کند که یک مرجع معتبر این داده‌ها را صادر کرده است.
  • هویت ارائه‌دهنده : تأیید فیلد cnf و امضای Key Binding ( 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 کار کند، توسعه‌دهندگان باید یک پل جاوااسکریپت (JS Bridge) پیاده‌سازی کنند تا انتقال اطلاعات را تسهیل کنند. این پل به Webview اجازه می‌دهد تا به برنامه بومی سیگنال دهد، که سپس می‌تواند فراخوانی واقعی را به API مدیریت اعتبار انجام دهد.

همچنین ببینید