سفارشی‌سازی جریان «درستی‌سنجی شماره تلفن Firebase» در Android

صفحه شروع به کار با Firebase Phone Number Verification جزئیات نحوه ادغام با Firebase PNV را بااستفاده از روش getVerifiedPhoneNumber() شرح می‌دهد که کل جریان Firebase PNV را از کسب رضایت کاربر تا برقراری تماس‌های شبکه لازم با پس‌بک Firebase PNV مدیریت می‌کند.

میانای برنامه‌سازی کاربردی تک‌روشی (getVerifiedPhoneNumber()) برای اکثر توسعه‌دهندگان توصیه می‌شود. بااین‌حال، اگر به کنترل دقیق‌تری بر تعامل با «مدیر اطلاعات کاربری Android» نیاز دارید (برای مثال، برای درخواست سایر اطلاعات کاربری همراه با شماره تلفن)، کتابخانه Firebase PNV دو روش زیر را نیز ارائه می‌دهد که هرکدام تعامل متفاوتی را با پس‌بخش Firebase PNV مدیریت می‌کنند:

  • getDigitalCredentialPayload() درخواستی با امضای سرور دریافت می‌کند که از آن برای فراخوانی «مدیر اطلاعات کاربری» استفاده خواهید کرد.
  • exchangeCredentialResponseForPhoneNumber() پاسخ Credential Manager را با یک توکن امضاشده حاوی شماره تلفن تأییدشده مبادله می‌کند. صدور صورت‌حساب در این مرحله انجام می‌شود.

بین فراخوانی هریک از این روش‌ها، شما مسئول مدیریت تعامل با Credential Manager API در Android هستید. این صفحه نمای کلی‌ای از نحوه پیاده‌سازی این جریان سه‌بخشی ارائه می‌دهد.

قبل از شروع

پروژه Firebase خود را راه‌اندازی کنید و وابستگی‌های Firebase PNV را همان‌طور که در صفحه شروع به کار توضیح داده شده است وارد کنید.

۱. دریافت بار داده درخواست «اعتبارنامه دیجیتال»

برای ایجاد درخواست شماره تلفن دستگاه، روش getDigitalCredentialPayload() را فراخوانی کنید. در مرحله بعد، این درخواست بار مفید تعامل شما با Credential Manager API خواهد بود.

Kotlin

// This instance does not require an Activity context.
val fpnv = FirebasePhoneNumberVerification.getInstance()

// Your request should include a nonce, which will propagate through the flow
// and be present in the final response from FPNV. See the section "Verifying
// the Firebase PNV token" for details on generating and verifying this.
val nonce = fetchNonceFromYourServer()

fpnv.getDigitalCredentialPayload(nonce)
  .addOnSuccessListener { fpnvDigitalCredentialPayload ->
    // Use the payload in the next step.
    // ...
  }
  .addOnFailureListener { e -> /* Handle payload fetch failure */ }

۲. ایجاد درخواست اطلاعات اعتباری دیجیتال بااستفاده از «مدیر اطلاعات اعتباری»

سپس، درخواست را به Credential Manager ارسال کنید.

برای انجام این کار، باید محموله درخواست را در درخواست DigitalCredential API بپیچید. این درخواست باید شامل همان نانسی باشد که به getDigitalCredentialPayload() ارسال کرده‌اید.

Kotlin

// This example uses string interpolation for clarity, but you should use some kind of type-safe
// serialization method.
fun buildDigitalCredentialRequestJson(nonce: String, fpnvDigitalCredentialPayload: String) = """
    {
      "requests": [
        {
          "protocol": "openid4vp-v1-unsigned",
          "data": {
            "response_type": "vp_token",
            "response_mode": "dc_api",
            "nonce": "$nonce",
            "dcql_query": { "credentials": [$fpnvDigitalCredentialPayload] }
          }
        }
      ]
    }
""".trimIndent()

پس‌از انجام این کار، می‌توانید بااستفاده از Credential Manager API درخواست را ارسال کنید:

Kotlin

suspend fun makeFpnvRequest(
  context: Activity, nonce: String, fpnvDigitalCredentialPayload: String): GetCredentialResponse {
  // Helper function to build the digital credential request (defined above).
  // Pass the same nonce you passed to getDigitalCredentialPayload().
  val digitalCredentialRequestJson =
    buildDigitalCredentialRequestJson(nonce, fpnvDigitalCredentialPayload)

  // CredentialManager requires an Activity context.
  val credentialManager = CredentialManager.create(context)

  // Build a Credential Manager request that includes the Firebase PNV option. Note that
  // you can't combine the digital credential option with other options.
  val request = GetCredentialRequest.Builder()
    .addCredentialOption(GetDigitalCredentialOption(digitalCredentialRequestJson))
    .build()

  // getCredential is a suspend function, so it must run in a coroutine scope,
  val cmResponse: GetCredentialResponse = try {
    credentialManager.getCredential(context, request)
  } catch (e: GetCredentialException) {
    // If the user cancels the operation, the feature isn't available, or the
    // SIM doesn't support the feature, a GetCredentialCancellationException
    // will be returned. Otherwise, a GetCredentialUnsupportedException will
    // be returned with details in the exception message.
    throw e
  }
  return cmResponse
}

اگر فراخوانی «مدیر اطلاعات کاربری» موفقیت‌آمیز باشد، پاسخ آن حاوی یک اعتبارنامه دیجیتال خواهد بود که می‌توانید آن را بااستفاده از کدی مانند مثال زیر استخراج کنید:

Kotlin

val dcApiResponse = extractApiResponse(cmResponse)
fun extractApiResponse(response: GetCredentialResponse): String {
  val credential = response.credential
  when (credential) {
    is DigitalCredential -> {
      val json = JSONObject(credential.credentialJson)
      val firebaseJwtArray =
          json.getJSONObject("data").getJSONObject("vp_token").getJSONArray("firebase")
      return firebaseJwtArray.getString(0)

    }
    else -> {
      // Handle any unrecognized credential type here.
      Log.e(TAG, "Unexpected type of credential ${credential.type}")
    }
  }
}

۳. مبادله پاسخ اطلاعات اعتباری دیجیتال با یک کد Firebase PNV

درنهایت، روش exchangeCredentialResponseForPhoneNumber() را فراخوانی کنید تا پاسخ اطلاعات کاربری دیجیتال را با شماره تلفن درستی‌سنجی‌شده و یک ژتون Firebase PNV مبادله کنید:

Kotlin

fpnv.exchangeCredentialResponseForPhoneNumber(dcApiResponse)
  .addOnSuccessListener { result ->
    val phoneNumber = result.getPhoneNumber()
    // Verification successful
  }
  .addOnFailureListener { e -> /* Handle exchange failure */ }

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

۴. درحال درستی‌سنجی کد Firebase PNV

اگر جریان موفقیت‌آمیز باشد، روش getVerifiedPhoneNumber() شماره تلفن درستی‌سنجی‌شده و یک توکن امضاشده حاوی آن را برمی‌گرداند. می‌توانید از این داده‌ها در برنامه‌تان طبق آنچه در خط‌مشی رازداری‌تان مستند شده است استفاده کنید.

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

  • نقطه پایانی تولید نانس
  • نقطه پایانی درستی‌سنجی کد

پیاده‌سازی این نقاط پایانی به شما بستگی دارد؛ مثال‌های زیر نشان می‌دهند چگونه می‌توانید آن‌ها را بااستفاده از Node.js و Express پیاده‌سازی کنید.

درحال تولید نانس

این نقطه پایانی مسئول تولید و ذخیره موقت مقادیر تک‌مصرفی به نام «نانس» است که برای جلوگیری از حملات تکراری علیه نقاط پایانی شما استفاده می‌شود. به‌عنوان مثال، ممکن است یک مسیر Express به این صورت تعریف شده باشد:

Node.js

app.get('/fpnvNonce', async (req, res) => {
    const nonce = crypto.randomUUID();

    // TODO: Save the nonce to a database, key store, etc.
    // You should also assign the nonce an expiration time and periodically
    // clear expired nonces from your database.
    await persistNonce({
        nonce,
        expiresAt: Date.now() + 180000, // Give it a short duration.
    });

    // Return the nonce to the caller.
    res.send({ nonce });
});

این نقطه پایانی است که تابع جای‌بان، fetchNonceFromYourServer()، در مرحله ۱ فراخوانی می‌کند. این عدد یک‌بارمصرف ازطریق تماس‌های شبکه‌ای مختلفی که کارخواه انجام می‌دهد منتشر می‌شود و درنهایت در قالب یک توکن Firebase PNV به سرور شما بازمی‌گردد. در مرحله بعد، درستی‌سنجی می‌کنید که توکن حاوی یک عدد یک‌بارمصرف است که شما تولید کرده‌اید.

درحال درستی‌سنجی کدها

این نقطه پایانی Firebase PNV توکن از مشتری شما دریافت می‌کند و اصالت آن‌ها را درستی‌سنجی می‌کند. برای درستی‌سنجی کردن یک توکن، باید موارد زیر را بررسی کنید:

  • سرصفحه typ روی JWT تنظیم شده است.

  • این توکن بااستفاده از یکی از کلیدهای منتشرشده در نقطه پایانی Firebase PNV JWKS با الگوریتم ES256 امضا شده است:

    https://fpnv.googleapis.com/v1beta/jwks
    
  • ادعاهای صادرکننده حاوی شماره پروژه Firebase شما است و در قالب زیر است:

    https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_NUMBER
    

    می‌توانید شماره پروژه Firebase خود را در تنظیمات > برگه عمومی کنسول Firebase پیدا کنید.

  • ادعای مخاطب فهرستی است که حاوی شماره پروژه Firebase و شناسه پروژه شما است و قالب آن به‌صورت زیر است:

    [
      https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_NUMBER,
      https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_ID,
    ]
    
  • کد منقضی نشده است.

  • توکن حاوی یک عدد یک‌بارمصرف معتبر است. یک نانس معتبر است اگر:

    • شما آن را تولید کرده‌اید (یعنی در هر سازوکار پایداری که استفاده می‌کنید، قابل یافتن است)
    • قبلاً استفاده نشده باشد
    • منقضی نشده است

برای مثال، پیاده‌سازی Express ممکن است چیزی شبیه به این باشد:

Node.js

import { JwtVerifier } from "aws-jwt-verify";

// Find your Firebase project number in the Firebase console.
const FIREBASE_PROJECT_NUMBER = "123456789";

// The issuer and audience claims of the FPNV token are specific to your
// project.
const issuer = `https://fpnv.googleapis.com/projects/${FIREBASE_PROJECT_NUMBER}`;
const audience = `https://fpnv.googleapis.com/projects/${FIREBASE_PROJECT_NUMBER}`;

// The JWKS URL contains the current public signing keys for FPNV tokens.
const jwksUri = "https://fpnv.googleapis.com/v1beta/jwks";

// Configure a JWT verifier to check the following:
// - The token is signed by Google
// - The issuer and audience claims match your project
// - The token has not yet expired (default begavior)
const fpnvVerifier = JwtVerifier.create({ issuer, audience, jwksUri });

app.post('/verifiedPhoneNumber', async (req, res) => {
    if (!req.body) return res.sendStatus(400);
    // Get the token from the body of the request.
    const fpnvToken = req.body;
    try {
        // Attempt to verify the token using the verifier configured above.
        const verifiedPayload = await fpnvVerifier.verify(fpnvToken);

        // Now that you've verified the signature and claims, verify the nonce.
        // TODO: Try to look up the nonce in your database and remove it if it's
        // found; if it's not found or it's expired, throw an error.
        await testAndRemoveNonce(verifiedPayload.nonce);

        // Only after verifying the JWT signature, claims, and nonce, get the
        // verified phone number from the subject claim.
        // You can use this value however it's needed by your app.
        const verifiedPhoneNumber = verifiedPayload.sub;
        // (Do something with it...)

        return res.sendStatus(200);
    } catch {
        // If verification fails, reject the token.
        return res.sendStatus(400);
    }
});