ขอข้อมูลเข้าสู่ระบบดิจิทัลใน WebView

คู่มือนี้อธิบายวิธีขอข้อมูลเข้าสู่ระบบดิจิทัลโดยใช้ Credential Manager ในแอป Android ที่ใช้ WebView ใช้ JavaScript Bridge เพื่ออำนวยความสะดวกในการสื่อสารระหว่างหน้าเว็บกับแอป

ภาพรวม

ขั้นตอนต่อไปนี้อธิบายวงจรของคำขอข้อมูลเข้าสู่ระบบดิจิทัลจาก WebView

  1. การสร้างคำขอ: หน้าเว็บใน WebView จะส่งคำขอไปยังเซิร์ฟเวอร์แอป ซึ่งจะสร้าง JSON คำขอ OpenID4VP และส่งกลับมา
  2. การเริ่มต้นคำขอ: หน้าเว็บใช้ navigator.credentials.get({ digital: ... }) เพื่อขอข้อมูลเข้าสู่ระบบดิจิทัลโดยใช้ JSON ของคำขอ
  3. การสกัดกั้น: bridge.js ที่แทรกจะสกัดกั้นการเรียกนี้ ดึงข้อมูล JSON ของคำขอ OpenID4VP ที่เฉพาะเจาะจง และส่งไปยังแอป Android
  4. การจัดการของ Android: แอป Android จะได้รับข้อความและเรียกใช้ API CredentialManager.getCredential
  5. การเลือกของผู้ใช้: Credential Manager จะแสดง UI ที่แจ้งให้ผู้ใช้แชร์ข้อมูลเข้าสู่ระบบ ระบบจะส่งข้อมูลเข้าสู่ระบบกลับไปยังแอป Android
  6. การตอบกลับของบริดจ์: แอป Android จะส่งการตอบกลับกลับไปยังหน้าเว็บโดยใช้บริดจ์
  7. เสร็จสมบูรณ์: หน้าเว็บได้รับและจัดการการตอบกลับ

สิ่งที่ต้องมีก่อน

ก่อนติดตั้งใช้งาน JavaScript Bridge สำหรับข้อมูลเข้าสู่ระบบดิจิทัลใน WebView โปรดตรวจสอบว่าโปรเจ็กต์ของคุณเป็นไปตามข้อกำหนดต่อไปนี้

  • ความเข้ากันได้ของเวอร์ชัน Android: แอปของคุณต้องกำหนดเป้าหมายเป็น Android 6.0 (API ระดับ 23) ขึ้นไป
  • การควบคุมแอปโฮสต์: คุณต้องควบคุมแอป Android ที่โฮสต์ WebView เพื่อให้แนบ WebMessageListener ก่อนโหลดหน้าเว็บได้
  • HTTPS และเฟรมระดับบนสุด: หน้าเว็บที่ขอข้อมูลเข้าสู่ระบบต้องแสดงผ่าน https:// และทำงานในเฟรมหลัก (isMainFrame ต้องเป็น true) ระบบไม่รองรับเฟรมย่อยและองค์ประกอบ <iframe> ที่ฝังอยู่
  • การเลือกใช้ API เวอร์ชันทดลอง: เนื่องจาก Digital Credentials API เป็นเวอร์ชันทดลอง คุณจึงต้องใส่คำอธิบายประกอบคลาสหรือเมธอดที่ใช้ GetDigitalCredentialOption ด้วย @OptIn(ExperimentalDigitalCredentialApi::class)

เพิ่มทรัพยากร Dependency

เพิ่มทรัพยากร Dependency ของ Credential Manager และ AndroidX WebKit ลงในไฟล์ build.gradle.kts ของโมดูลแอป

dependencies {
    // Credential Manager dependencies
    implementation("androidx.credentials:credentials:1.7.0-alpha03")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")

    // AndroidX WebKit (required for WebMessageListener bridge)
    implementation("androidx.webkit:webkit:1.14.0")
}

ใช้ข้อมูลเข้าสู่ระบบดิจิทัลใน WebView

คุณจะต้องเพิ่ม JavaScript ลงใน WebView เพื่อส่งคำขอข้อมูลเข้าสู่ระบบและสกัดกั้นด้วย bridge.js

สร้างคำขอ JavaScript

หากต้องการสื่อสารระหว่าง JavaScript ที่ทำงานใน WebView กับโค้ด Android ให้แทรกโค้ด JavaScript ลงใน WebView ดังนี้

// Example digital credential request to request verified email
const request = {
  "providers": [
      {
        "protocol": "openid4vp",
        "request": JSON.stringify({
          "requests": [
            {
              "protocol": "openid4vp-v1-unsigned",
              "data": {
                "response_type": "vp_token",
                "response_mode": "dc_api",
                "nonce": "n-0S6_W_2F", // Generate dynamically
                "dcql_query": {
                  "credentials": [
                    {
                      "id": "user_info_query",
                      "format": "dc+sd-jwt",
                      "meta": {
                        "vct_values": ["UserInfoCredential"]
                      },
                      "claims": [
                        {"path": ["email"]},
                        {"path": ["email_verified"]},
                        {"path": ["name"]}
                      ]
                    }
                  ]
                }
              }
            }
          ]
      })
    }
  ]
};

try {
  const credential = await navigator.credentials.get({
    digital: request
  });

  // Process response
} catch (err) {
  // Handle error
}

สร้าง JavaScript Bridge

สร้างไฟล์ชื่อ bridge.js หรือแทรกโค้ดนี้โดยใช้ evaluateJavascript สคริปต์นี้จะลบล้าง navigator.credentials.get เพื่อสกัดกั้นคำขอและส่งสตริง JSON ของ OpenID4VP ภายในไปยังแอป Android

(function() {
  if (!navigator.credentials) {
    navigator.credentials = {};
  }

  const originalGet = navigator.credentials.get;

  // Overrides navigator.credentials.get() to intercept W3C Digital Credential requests.
  navigator.credentials.get = async function(options) {

    if (options && options.digital) {
      if (window.DigitalCredentialBridge) {
        return new Promise((resolve, reject) => {
          const digital = options.digital;

          if (digital.providers && digital.providers.length > 0) {
            const provider = digital.providers[0];

            if (provider.protocol === "openid4vp") {
              const requestJson = provider.request;

              const message = {
                type: "get",
                request: requestJson
              };

              console.log("Sending message to native bridge:", JSON.stringify(message));
              // Send the request string to native Android code
              window.DigitalCredentialBridge.postMessage(JSON.stringify(message));

              // Receive the response sent from native code using replyProxy.postMessage()
              window.DigitalCredentialBridge.onmessage = function(event) {
                console.log("Received response from native bridge:", event.data);
                try {
                  const response = JSON.parse(event.data);
                  if (response.error) {
                    reject(new Error(response.error));
                  } else {
                    resolve(response);
                  }
                } catch (e) {
                  // If it's not JSON, just resolve with the raw data
                  resolve(event.data);
                }
              };
            } else {
              reject(new Error("Unsupported protocol: " + provider.protocol));
            }
          } else {
            reject(new Error("No providers specified in digital credential request"));
          }
        });
      } else {
        console.warn("DigitalCredentialBridge not found, falling back to original");
      }
    }

    if (originalGet) {
      return originalGet.call(navigator.credentials, options);
    } else {
      throw new Error("navigator.credentials.get is not supported and no bridge available");
    }
  };
})();

ใช้เอกสารรับรองดิจิทัลใน Android

กำหนดค่าแอป Android ให้จัดการคำขอข้อมูลเข้าสู่ระบบที่ส่งจาก bridge.js

สร้าง Listener ของ Android

ตั้งค่าคลาส DigitalCredentialWebListener ที่ใช้ WebViewCompat.WebMessageListener คลาสนี้จะจัดการข้อความแบบ 2 ทิศทางระหว่าง JavaScript กับโค้ด Android แบบเนทีฟ โดยบังคับใช้การตรวจสอบความปลอดภัยใน isMainFrame และ sourceOrigin รวมถึงป้องกันคำขอที่ทับซ้อนกัน

ใช้เครื่องจัดการ Credential Manager

เรียกใช้ getCredential เพื่อขอข้อมูลเข้าสู่ระบบดิจิทัลจากอุปกรณ์

@OptIn(ExperimentalDigitalCredentialApi::class)
class CredentialManagerHandler(private val context: Context) {
  private val credentialManager = CredentialManager.create(context)

  suspend fun getDigitalCredential(requestJson: String): String {
    val digitalCredentialOption = GetDigitalCredentialOption(requestJson)
    val getCredRequest = GetCredentialRequest(listOf(digitalCredentialOption))

    return try {
      val result = credentialManager.getCredential(context, getCredRequest)
      val credential = result.credential
      if (credential is DigitalCredential) {
        credential.credentialJson
      } else {
        "{\"error\": \"Unexpected credential type\"}"
      }
    } catch (e: GetCredentialException) {
      "{\"error\": \"${e.message}\"}"
    } catch (e: Exception) {
      "{\"error\": \"Unknown exception\"}"
    }
  }

  companion object {
    private const val TAG = "CredManHandler"
  }
}

ใช้เฟรมเวิร์กกับกิจกรรมของคุณ

ลงทะเบียน WebMessageListener และฟังก์ชันที่เกี่ยวข้องใน Activity หรือ View

// Inside your Activity or View setup
AndroidView(
  factory = { context ->
    val handler = CredentialManagerHandler(context)
    WebView(context).apply {
      settings.javaScriptEnabled = true

      // Register the bridge before loading URL
      if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
                val listener = DigitalCredentialWebListener(coroutineScope, credentialManagerHandler)
                WebViewCompat.addWebMessageListener(
                    webView,
                    INTERFACE_NAME,
                    setOf("*"),
                    listener
                )


      loadUrl("https://your-secure-domain.com/login")
    }
  },
)

กำลังทดสอบโฟลว์

ทดสอบขั้นตอนเพื่อยืนยันการสื่อสารระหว่าง WebView กับแอป Android ตัวเลือกข้อมูลเข้าสู่ระบบจะปรากฏใน WebView ระหว่างกระบวนการ และข้อมูลเข้าสู่ระบบที่เลือกจะกลับไปยัง WebView เมื่อดำเนินการเสร็จสมบูรณ์