ลงชื่อเข้าใช้ผู้ใช้ด้วยตัวจัดการข้อมูลเข้าสู่ระบบ

Credential Manager คือ Jetpack API ที่รองรับวิธีการลงชื่อเข้าใช้หลายวิธี เช่น ชื่อผู้ใช้และรหัสผ่าน พาสคีย์ และโซลูชันการลงชื่อเข้าใช้แบบรวมศูนย์ (เช่น ฟีเจอร์ลงชื่อเข้าใช้ด้วย Google) ใน API เดียว ซึ่งทำให้นักพัฒนาแอปผสานรวมได้ง่ายขึ้น

นอกจากนี้ Credential Manager ยังรวบรวมอินเทอร์เฟซการลงชื่อเข้าใช้ต่างๆ เข้าด้วยกัน ทั้งวิธีการตรวจสอบสิทธิ์ต่างๆ เพื่อให้การลงชื่อเข้าใช้แอปชัดเจนและง่ายขึ้น ไม่ว่าผู้ใช้จะเลือกวิธีการใดก็ตาม

หน้านี้อธิบายแนวคิดของพาสคีย์และขั้นตอนในการใช้งานการสนับสนุนฝั่งไคลเอ็นต์สำหรับโซลูชันการตรวจสอบสิทธิ์ ซึ่งรวมถึงพาสคีย์ โดยใช้ Credential Manager API นอกจากนี้ยังมีหน้าคำถามที่พบบ่อยแยกต่างหากซึ่งให้คำตอบสำหรับคำถามที่เฉพาะเจาะจงและละเอียดยิ่งขึ้น

ความคิดเห็นของคุณเป็นส่วนสําคัญในการปรับปรุง Credential Manager API แชร์ปัญหาที่พบหรือไอเดียในการปรับปรุง API โดยใช้ลิงก์ต่อไปนี้

แสดงความคิดเห็น

เกี่ยวกับพาสคีย์

พาสคีย์เป็นวิธีแทนที่รหัสผ่านได้อย่างปลอดภัยและง่ายดายยิ่งขึ้น เมื่อใช้พาสคีย์ ผู้ใช้จะลงชื่อเข้าใช้แอปและเว็บไซต์ได้โดยใช้เซ็นเซอร์ข้อมูลไบโอเมตริก (เช่น การจดจำลายนิ้วมือหรือใบหน้า), PIN หรือรูปแบบ วิธีนี้ช่วยให้ผู้ใช้ลงชื่อเข้าใช้ได้อย่างราบรื่นโดยไม่ต้องจดจำชื่อผู้ใช้หรือรหัสผ่าน

พาสคีย์ใช้ WebAuthn (การตรวจสอบสิทธิ์ผ่านเว็บ) ซึ่งเป็นมาตรฐานที่ FIDO Alliance และ World Wide Web Consortium (W3C) พัฒนาร่วมกัน WebAuthn ใช้วิทยาการเข้ารหัสคีย์สาธารณะเพื่อตรวจสอบสิทธิ์ผู้ใช้ เว็บไซต์หรือแอปที่ผู้ใช้ลงชื่อเข้าใช้จะเห็นและจัดเก็บคีย์สาธารณะได้ แต่จะไม่เห็นคีย์ส่วนตัว คีย์ส่วนตัวจะเก็บเป็นความลับและปลอดภัย และเนื่องจากพาสคีย์เป็นคีย์ที่ไม่ซ้ำกันและเชื่อมโยงกับเว็บไซต์หรือแอป จึงทำให้ไม่สามารถฟิชชิงได้ ซึ่งช่วยเพิ่มความปลอดภัยอีกขั้น

เครื่องมือจัดการข้อมูลเข้าสู่ระบบช่วยให้ผู้ใช้สร้างพาสคีย์และจัดเก็บไว้ในเครื่องมือจัดการรหัสผ่านบน Google ได้

โปรดอ่านการตรวจสอบสิทธิ์ผู้ใช้ด้วยพาสคีย์เพื่อดูคำแนะนำเกี่ยวกับวิธีใช้ขั้นตอนการตรวจสอบสิทธิ์พาสคีย์ที่ราบรื่นด้วยเครื่องมือจัดการข้อมูลเข้าสู่ระบบ

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

หากต้องการใช้เครื่องมือจัดการข้อมูลเข้าสู่ระบบ ให้ทำตามขั้นตอนในส่วนนี้

ใช้แพลตฟอร์มเวอร์ชันล่าสุด

เครื่องมือจัดการข้อมูลเข้าสู่ระบบใช้ได้ใน Android 4.4 (API ระดับ 19) ขึ้นไป

เพิ่มการพึ่งพาไปยังแอป

เพิ่มการพึ่งพาต่อไปนี้ลงในสคริปต์บิลด์ของโมดูลแอป

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.5.0-alpha05")

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation("androidx.credentials:credentials-play-services-auth:1.5.0-alpha05")
}

ดึงดูด

dependencies {
    implementation "androidx.credentials:credentials:1.5.0-alpha05"

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation "androidx.credentials:credentials-play-services-auth:1.5.0-alpha05"
}

เก็บรักษาคลาสในไฟล์ ProGuard

ในไฟล์ proguard-rules.pro ของโมดูล ให้เพิ่มคำสั่งต่อไปนี้

-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
  *;
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีลดขนาด สร้างความสับสน และเพิ่มประสิทธิภาพแอป

เพิ่มการรองรับลิงก์เนื้อหาดิจิทัล

หากต้องการเปิดใช้การรองรับพาสคีย์สําหรับแอป Android ให้เชื่อมโยงแอปกับเว็บไซต์ที่แอปเป็นเจ้าของ คุณสามารถประกาศการเชื่อมโยงนี้ได้โดยทำตามขั้นตอนต่อไปนี้

  1. สร้างไฟล์ JSON ของลิงก์เนื้อหาดิจิทัล (Digital Asset Links) ตัวอย่างเช่น หากต้องการประกาศว่าเว็บไซต์ https://signin.example.com และแอป Android ที่มีชื่อแพ็กเกจ com.example สามารถแชร์ข้อมูลเข้าสู่ระบบ ให้สร้างไฟล์ชื่อ assetlinks.json ที่มีเนื้อหาดังต่อไปนี้

    [
      {
        "relation" : [
          "delegate_permission/common.handle_all_urls",
          "delegate_permission/common.get_login_creds"
        ],
        "target" : {
          "namespace" : "android_app",
          "package_name" : "com.example.android",
          "sha256_cert_fingerprints" : [
            SHA_HEX_VALUE
          ]
        }
      }
    ]
    

    ช่อง relation คืออาร์เรย์สตริงอย่างน้อย 1 รายการที่อธิบายความสัมพันธ์ที่ประกาศ หากต้องการประกาศว่าแอปและเว็บไซต์ใช้ข้อมูลเข้าสู่ระบบร่วมกัน ให้ระบุความสัมพันธ์เป็น delegate_permission/handle_all_urls และ delegate_permission/common.get_login_creds

    ฟิลด์ target คือออบเจ็กต์ที่ระบุเนื้อหาที่ใช้ประกาศ ฟิลด์ต่อไปนี้จะระบุเว็บไซต์

    namespace web
    site

    URL ของเว็บไซต์ในรูปแบบ https://domain[:optional_port] เช่น https://www.example.com

    domain ต้องระบุอย่างเต็มรูปแบบ และจะต้องไม่ใส่ optional_port เมื่อใช้พอร์ต 443 สําหรับ HTTPS

    เป้าหมาย site ต้องเป็นโดเมนรูทเท่านั้น คุณไม่สามารถจํากัดการเชื่อมโยงแอปกับไดเรกทอรีย่อยที่เฉพาะเจาะจงได้ อย่าใส่เส้นทางใน URL เช่น เครื่องหมายทับต่อท้าย

    ระบบจะไม่ถือว่าโดเมนย่อยตรงกัน กล่าวคือ หากคุณระบุ domain เป็น www.example.com โดเมน www.counter.example.com จะไม่เชื่อมโยงกับแอปของคุณ

    ฟิลด์ต่อไปนี้จะระบุแอป Android

    namespace android_app
    package_name ชื่อแพ็กเกจที่ประกาศในไฟล์ Manifest ของแอป เช่น com.example.android
    sha256_cert_fingerprints ลายนิ้วมือ SHA256 ของใบรับรองการลงนามของแอป
  2. โฮสต์ไฟล์ JSON ของลิงก์เนื้อหาดิจิทัล (Digital Assets Link) ที่ตำแหน่งต่อไปนี้ในโดเมนการลงชื่อเข้าใช้

    https://domain[:optional_port]/.well-known/assetlinks.json
    

    เช่น หากโดเมนการลงชื่อเข้าใช้คือ signin.example.com ให้โฮสต์ไฟล์ JSON ที่ https://signin.example.com/.well-known/assetlinks.json

    ประเภท MIME สำหรับไฟล์ลิงก์เนื้อหาดิจิทัล (Digital Asset Links) ต้องเป็น JSON ตรวจสอบว่าเซิร์ฟเวอร์ส่งส่วนหัว Content-Type: application/json ในการตอบกลับ

  3. ตรวจสอบว่าโฮสต์อนุญาตให้ Google เรียกข้อมูลไฟล์ลิงก์เนื้อหาดิจิทัล (Digital Asset Links) หากคุณมีไฟล์ robots.txt คุณต้องอนุญาตให้ Agent ของ Googlebot ดึงข้อมูล /.well-known/assetlinks.json ได้ เว็บไซต์ส่วนใหญ่อนุญาตให้ตัวแทนอัตโนมัติดึงข้อมูลไฟล์ในเส้นทาง /.well-known/ เพื่อให้บริการอื่นๆ เข้าถึงข้อมูลเมตาในไฟล์เหล่านั้นได้ โดยทำดังนี้

    User-agent: *
    Allow: /.well-known/
    
  4. เพิ่มบรรทัดต่อไปนี้ลงในไฟล์ Manifest ในส่วน <application>

    <meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
    
  5. หากคุณใช้การลงชื่อเข้าใช้ด้วยรหัสผ่านผ่านเครื่องมือจัดการข้อมูลเข้าสู่ระบบ ให้ทำตามขั้นตอนนี้เพื่อกำหนดค่าการลิงก์เนื้อหาดิจิทัลในไฟล์ Manifest ขั้นตอนนี้ไม่จำเป็นหากใช้พาสคีย์เพียงอย่างเดียว

    ประกาศการเชื่อมโยงในแอป Android เพิ่มออบเจ็กต์ที่ระบุไฟล์ assetlinks.json ที่จะโหลด คุณต้องไม่ใช้เครื่องหมายอะพอสทรอฟีและเครื่องหมายอัญประกาศที่ใช้ในสตริง เช่น

    <string name="asset_statements" translatable="false">
    [{
      \"include\": \"https://signin.example.com/.well-known/assetlinks.json\"
    }]
    </string>
    
    > GET /.well-known/assetlinks.json HTTP/1.1
    > User-Agent: curl/7.35.0
    > Host: signin.example.com
    
    < HTTP/1.1 200 OK
    < Content-Type: application/json
    

กำหนดค่าเครื่องมือจัดการข้อมูลเข้าสู่ระบบ

หากต้องการกําหนดค่าและเริ่มต้นวัตถุ CredentialManager ให้เพิ่มตรรกะคล้ายกับตัวอย่างต่อไปนี้

Kotlin

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
val credentialManager = CredentialManager.create(context)

Java

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
CredentialManager credentialManager = CredentialManager.create(context)

ระบุช่องข้อมูลเข้าสู่ระบบ

ใน Android 14 ขึ้นไป คุณสามารถใช้แอตทริบิวต์ isCredential เพื่อระบุช่องข้อมูลเข้าสู่ระบบ เช่น ช่องชื่อผู้ใช้หรือรหัสผ่าน แอตทริบิวต์นี้ระบุว่ามุมมองนี้เป็นช่องข้อมูลเข้าสู่ระบบที่มีไว้เพื่อใช้งานกับเครื่องมือจัดการข้อมูลเข้าสู่ระบบและผู้ให้บริการข้อมูลเข้าสู่ระบบบุคคลที่สาม ในขณะที่ช่วยให้บริการป้อนข้อความอัตโนมัติให้คำแนะนำเกี่ยวกับการป้อนข้อความอัตโนมัติที่ดียิ่งขึ้น เมื่อแอปใช้ API เครื่องมือจัดการข้อมูลเข้าสู่ระบบ Bottom Sheet ของเครื่องมือจัดการข้อมูลเข้าสู่ระบบซึ่งมีข้อมูลเข้าสู่ระบบที่ใช้ได้จะปรากฏขึ้น และไม่จําเป็นต้องแสดงกล่องโต้ตอบการกรอกของการป้อนข้อความอัตโนมัติสําหรับชื่อผู้ใช้หรือรหัสผ่านอีก ในทํานองเดียวกัน คุณไม่จําเป็นต้องแสดงกล่องโต้ตอบบันทึกของฟีเจอร์ป้อนข้อความอัตโนมัติสําหรับรหัสผ่าน เนื่องจากแอปจะขอ Credential Manager API เพื่อบันทึกข้อมูลเข้าสู่ระบบ

หากต้องการใช้แอตทริบิวต์ isCredential ให้เพิ่มแอตทริบิวต์นั้นลงในข้อมูลพร็อพเพอร์ตี้ที่เกี่ยวข้อง ดังนี้

<TextView
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:isCredential="true"
...
 />

ลงชื่อเข้าใช้ผู้ใช้

หากต้องการเรียกข้อมูลพาสคีย์และตัวเลือกรหัสผ่านทั้งหมดที่เชื่อมโยงกับบัญชีของผู้ใช้ ให้ทำตามขั้นตอนต่อไปนี้

  1. เริ่มต้นตัวเลือกการตรวจสอบสิทธิ์ด้วยรหัสผ่านและพาสคีย์ ดังนี้

    Kotlin

    // Retrieves the user's saved password for your app from their
    // password provider.
    val getPasswordOption = GetPasswordOption()
    
    // Get passkey from the user's public key credential provider.
    val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
        requestJson = requestJson
    )

    Java

    // Retrieves the user's saved password for your app from their
    // password provider.
    GetPasswordOption getPasswordOption = new GetPasswordOption();
    
    // Get passkey from the user's public key credential provider.
    GetPublicKeyCredentialOption getPublicKeyCredentialOption =
            new GetPublicKeyCredentialOption(requestJson);
  2. ใช้ตัวเลือกที่ดึงมาจากขั้นตอนก่อนหน้าเพื่อสร้างคำขอลงชื่อเข้าใช้

    Kotlin

    val getCredRequest = GetCredentialRequest(
        listOf(getPasswordOption, getPublicKeyCredentialOption)
    )

    Java

    GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder()
        .addCredentialOption(getPasswordOption)
        .addCredentialOption(getPublicKeyCredentialOption)
        .build();
  3. เปิดขั้นตอนการลงชื่อเข้าใช้

    Kotlin

    coroutineScope.launch {
        try {
            val result = credentialManager.getCredential(
                // Use an activity-based context to avoid undefined system UI
                // launching behavior.
                context = activityContext,
                request = getCredRequest
            )
            handleSignIn(result)
        } catch (e : GetCredentialException) {
            handleFailure(e)
        }
    }
    
    fun handleSignIn(result: GetCredentialResponse) {
        // Handle the successfully returned credential.
        val credential = result.credential
    
        when (credential) {
            is PublicKeyCredential -> {
                val responseJson = credential.authenticationResponseJson
                // Share responseJson i.e. a GetCredentialResponse on your server to
                // validate and  authenticate
            }
            is PasswordCredential -> {
                val username = credential.id
                val password = credential.password
                // Use id and password to send to your server to validate
                // and authenticate
            }
          is CustomCredential -> {
              // If you are also using any external sign-in libraries, parse them
              // here with the utility functions provided.
              if (credential.type == ExampleCustomCredential.TYPE)  {
              try {
                  val ExampleCustomCredential = ExampleCustomCredential.createFrom(credential.data)
                  // Extract the required credentials and complete the authentication as per
                  // the federated sign in or any external sign in library flow
                  } catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) {
                      // Unlikely to happen. If it does, you likely need to update the dependency
                      // version of your external sign-in library.
                      Log.e(TAG, "Failed to parse an ExampleCustomCredential", e)
                  }
              } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential")
              }
            } else -> {
                // Catch any unrecognized credential type here.
                Log.e(TAG, "Unexpected type of credential")
            }
        }
    }

    Java

    credentialManager.getCredentialAsync(
        // Use activity based context to avoid undefined
        // system UI launching behavior
        activity,
        getCredRequest,
        cancellationSignal,
        <executor>,
        new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
            @Override
            public void onResult(GetCredentialResponse result) {
                handleSignIn(result);
            }
    
            @Override
            public void onError(GetCredentialException e) {
                handleFailure(e);
            }
        }
    );
    
    public void handleSignIn(GetCredentialResponse result) {
        // Handle the successfully returned credential.
        Credential credential = result.getCredential();
        if (credential instanceof PublicKeyCredential) {
            String responseJson = ((PublicKeyCredential) credential).getAuthenticationResponseJson();
            // Share responseJson i.e. a GetCredentialResponse on your server to validate and authenticate
        } else if (credential instanceof PasswordCredential) {
            String username = ((PasswordCredential) credential).getId();
            String password = ((PasswordCredential) credential).getPassword();
            // Use id and password to send to your server to validate and authenticate
        } else if (credential instanceof CustomCredential) {
            if (ExampleCustomCredential.TYPE.equals(credential.getType())) {
                try {
                    ExampleCustomCredential customCred = ExampleCustomCredential.createFrom(customCredential.getData());
                    // Extract the required credentials and complete the
                    // authentication as per the federated sign in or any external
                    // sign in library flow
                } catch (ExampleCustomCredential.ExampleCustomCredentialParsingException e) {
                    // Unlikely to happen. If it does, you likely need to update the
                    // dependency version of your external sign-in library.
                    Log.e(TAG, "Failed to parse an ExampleCustomCredential", e);
                }
            } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential");
            }
        } else {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential");
        }
    }

ตัวอย่างต่อไปนี้แสดงวิธีจัดรูปแบบคำขอ JSON เมื่อคุณได้รับพาสคีย์

{
  "challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
  "allowCredentials": [],
  "timeout": 1800000,
  "userVerification": "required",
  "rpId": "credential-manager-app-test.glitch.me"
}

ตัวอย่างต่อไปนี้แสดงลักษณะของการตอบกลับ JSON หลังจากที่คุณได้รับข้อมูลเข้าสู่ระบบด้วยคีย์สาธารณะ

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
    "signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
    "userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
  }
}

จัดการข้อยกเว้นเมื่อไม่มีข้อมูลเข้าสู่ระบบ

ในบางกรณี ผู้ใช้อาจไม่มีข้อมูลเข้าสู่ระบบหรืออาจไม่ให้ความยินยอมในการใช้ข้อมูลเข้าสู่ระบบที่มีอยู่ หากมีการเรียกใช้ getCredential() และไม่พบข้อมูลเข้าสู่ระบบ ระบบจะแสดงผล NoCredentialException ในกรณีนี้ รหัสของคุณควรจัดการNoCredentialException อินสแตนซ์

Kotlin

try {
  val credential = credentialManager.getCredential(credentialRequest)
} catch (e: NoCredentialException) {
  Log.e("CredentialManager", "No credential available", e)
}

Java

try {
  Credential credential = credentialManager.getCredential(credentialRequest);
} catch (NoCredentialException e) {
  Log.e("CredentialManager", "No credential available", e);
}

ใน Android 14 ขึ้นไป คุณสามารถลดเวลาในการตอบสนองเมื่อแสดงตัวเลือกบัญชีได้โดยใช้เมธอด prepareGetCredential() ก่อนเรียกใช้ getCredential()

Kotlin

val response = credentialManager.prepareGetCredential(
  GetCredentialRequest(
    listOf(
      <getPublicKeyCredentialOption>,
      <getPasswordOption>
    )
  )
}

Java

GetCredentialResponse response = credentialManager.prepareGetCredential(
  new GetCredentialRequest(
    Arrays.asList(
      new PublicKeyCredentialOption(),
      new PasswordOption()
    )
  )
);

เมธอด prepareGetCredential() จะไม่เรียกใช้องค์ประกอบ UI การดำเนินการนี้จะช่วยคุณเตรียมงานเพื่อให้สามารถเปิดใช้งานการดำเนินการรับข้อมูลเข้าสู่ระบบที่เหลือ (ซึ่งเกี่ยวข้องกับ UI) ผ่าน getCredential() API ในภายหลัง

ระบบจะแสดงข้อมูลที่แคชไว้ในออบเจ็กต์ PrepareGetCredentialResponse หากมีข้อมูลเข้าสู่ระบบอยู่แล้ว ระบบจะแคชผลลัพธ์ไว้ จากนั้นคุณจะเปิด getCredential() API ที่เหลือในภายหลังเพื่อแสดงตัวเลือกบัญชีที่มีข้อมูลที่แคชไว้ได้

ขั้นตอนการลงทะเบียน

คุณสามารถลงทะเบียนผู้ใช้เพื่อตรวจสอบสิทธิ์ได้โดยใช้พาสคีย์หรือรหัสผ่าน

สร้างพาสคีย์

หากต้องการให้ผู้ใช้มีทางเลือกในการลงทะเบียนพาสคีย์และใช้พาสคีย์นั้นในการตรวจสอบสิทธิ์อีกครั้ง ให้ลงทะเบียนข้อมูลเข้าสู่ระบบของผู้ใช้โดยใช้ออบเจ็กต์ CreatePublicKeyCredentialRequest

Kotlin

fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) {
    val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(
        // Contains the request in JSON format. Uses the standard WebAuthn
        // web JSON spec.
        requestJson = requestJson,
        // Defines whether you prefer to use only immediately available
        // credentials, not hybrid credentials, to fulfill this request.
        // This value is false by default.
        preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
    )

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    coroutineScope.launch {
        try {
            val result = credentialManager.createCredential(
                // Use an activity-based context to avoid undefined system
                // UI launching behavior
                context = activityContext,
                request = createPublicKeyCredentialRequest,
            )
            handlePasskeyRegistrationResult(result)
        } catch (e : CreateCredentialException){
            handleFailure(e)
        }
    }
}

fun handleFailure(e: CreateCredentialException) {
    when (e) {
        is CreatePublicKeyCredentialDomException -> {
            // Handle the passkey DOM errors thrown according to the
            // WebAuthn spec.
            handlePasskeyError(e.domError)
        }
        is CreateCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to register the credential.
        }
        is CreateCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is CreateCredentialProviderConfigurationException -> {
            // Your app is missing the provider configuration dependency.
            // Most likely, you're missing the
            // "credentials-play-services-auth" module.
        }
        is CreateCredentialUnknownException -> ...
        is CreateCredentialCustomException -> {
            // You have encountered an error from a 3rd-party SDK. If you
            // make the API call with a request object that's a subclass of
            // CreateCustomCredentialRequest using a 3rd-party SDK, then you
            // should check for any custom exception type constants within
            // that SDK to match with e.type. Otherwise, drop or log the
            // exception.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
    }
}

Java

public void createPasskey(String requestJson, boolean preferImmediatelyAvailableCredentials) {
    CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest =
            // `requestJson` contains the request in JSON format. Uses the standard
            // WebAuthn web JSON spec.
            // `preferImmediatelyAvailableCredentials` defines whether you prefer
            // to only use immediately available credentials, not  hybrid credentials,
            // to fulfill this request. This value is false by default.
            new CreatePublicKeyCredentialRequest(
                requestJson, preferImmediatelyAvailableCredentials);

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined system
        // UI launching behavior
        requireActivity(),
        createPublicKeyCredentialRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleSuccessfulCreatePasskeyResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                if (e instanceof CreatePublicKeyCredentialDomException) {
                    // Handle the passkey DOM errors thrown according to the
                    // WebAuthn spec.
                    handlePasskeyError(((CreatePublicKeyCredentialDomException)e).getDomError());
                } else if (e instanceof CreateCredentialCancellationException) {
                    // The user intentionally canceled the operation and chose not
                    // to register the credential.
                } else if (e instanceof CreateCredentialInterruptedException) {
                    // Retry-able error. Consider retrying the call.
                } else if (e instanceof CreateCredentialProviderConfigurationException) {
                    // Your app is missing the provider configuration dependency.
                    // Most likely, you're missing the
                    // "credentials-play-services-auth" module.
                } else if (e instanceof CreateCredentialUnknownException) {
                } else if (e instanceof CreateCredentialCustomException) {
                    // You have encountered an error from a 3rd-party SDK. If
                    // you make the API call with a request object that's a
                    // subclass of
                    // CreateCustomCredentialRequest using a 3rd-party SDK,
                    // then you should check for any custom exception type
                    // constants within that SDK to match with e.type.
                    // Otherwise, drop or log the exception.
                } else {
                  Log.w(TAG, "Unexpected exception type "
                          + e.getClass().getName());
                }
            }
        }
    );
}

จัดรูปแบบคำขอ JSON

หลังจากสร้างพาสคีย์แล้ว คุณต้องเชื่อมโยงพาสคีย์กับบัญชีของผู้ใช้ และเก็บคีย์สาธารณะของพาสคีย์ไว้ในเซิร์ฟเวอร์ ตัวอย่างโค้ดต่อไปนี้แสดงวิธีจัดรูปแบบคำขอ JSON เมื่อคุณสร้างพาสคีย์

บล็อกโพสต์เกี่ยวกับการทำให้แอปของคุณมีการตรวจสอบสิทธิ์ที่ราบรื่นนี้จะแสดงวิธีจัดรูปแบบคำขอ JSON เมื่อคุณสร้างพาสคีย์และเมื่อคุณตรวจสอบสิทธิ์โดยใช้พาสคีย์ นอกจากนี้ยังอธิบายสาเหตุที่รหัสผ่านไม่ใช่โซลูชันการตรวจสอบสิทธิ์ที่มีประสิทธิภาพ วิธีใช้ประโยชน์จากข้อมูลเข้าสู่ระบบข้อมูลไบโอเมตริกที่มีอยู่ วิธีเชื่อมโยงแอปกับเว็บไซต์ที่คุณเป็นเจ้าของ วิธีสร้างพาสคีย์ และวิธีตรวจสอบสิทธิ์โดยใช้พาสคีย์

{
  "challenge": "abc123",
  "rp": {
    "name": "Credential Manager example",
    "id": "credential-manager-test.example.com"
  },
  "user": {
    "id": "def456",
    "name": "helloandroid@gmail.com",
    "displayName": "helloandroid@gmail.com"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {"id": "ghi789", "type": "public-key"},
    {"id": "jkl012", "type": "public-key"}
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

กำหนดค่าสำหรับ authenticatorAttachment

ตั้งค่าพารามิเตอร์ authenticatorAttachment ได้ในเวลาที่สร้างข้อมูลเข้าสู่ระบบเท่านั้น คุณสามารถระบุ platform, cross-platform หรือไม่มีค่า ในกรณีส่วนใหญ่ เราขอแนะนำให้ไม่ระบุค่า

  • platform: หากต้องการลงทะเบียนอุปกรณ์ปัจจุบันของผู้ใช้หรือแจ้งให้ผู้ใช้ที่มีรหัสผ่านอัปเกรดเป็นพาสคีย์หลังจากลงชื่อเข้าใช้ ให้ตั้งค่า authenticatorAttachment เป็น platform
  • cross-platform: ค่านี้มักใช้เมื่อลงทะเบียนข้อมูลเข้าสู่ระบบแบบหลายปัจจัย และไม่ใช้ในบริบทของพาสคีย์
  • ไม่มีค่า: เพื่อมอบความยืดหยุ่นให้กับผู้ใช้ในการสร้างพาสคีย์ในอุปกรณ์ที่ต้องการ (เช่น ในการตั้งค่าบัญชี) คุณไม่ควรระบุพารามิเตอร์ authenticatorAttachment เมื่อผู้ใช้เลือกที่จะเพิ่มพาสคีย์ ในกรณีส่วนใหญ่ การไม่ระบุพารามิเตอร์เป็นตัวเลือกที่ดีที่สุด

ป้องกันไม่ให้สร้างพาสคีย์ซ้ำ

แสดงรหัสข้อมูลเข้าสู่ระบบในอาร์เรย์ excludeCredentials (ไม่บังคับ) เพื่อป้องกันการสร้างพาสคีย์ใหม่หากมีพาสคีย์ที่มีผู้ให้บริการพาสคีย์เดียวกันอยู่แล้ว

จัดการการตอบกลับ JSON

ข้อมูลโค้ดต่อไปนี้แสดงตัวอย่างการตอบสนอง JSON สำหรับการสร้างข้อมูลเข้าสู่ระบบคีย์สาธารณะ ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีจัดการข้อมูลเข้าสู่ระบบคีย์สาธารณะที่แสดงผล

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
  }
}

ยืนยันต้นทางจาก JSON ของข้อมูลไคลเอ็นต์

origin แสดงถึงแอปพลิเคชันหรือเว็บไซต์ที่เป็นแหล่งที่มาของคำขอ และใช้โดยพาสคีย์เพื่อป้องกันการโจมตีแบบฟิชชิง เซิร์ฟเวอร์ของแอปต้องตรวจสอบแหล่งที่มาของข้อมูลไคลเอ็นต์กับรายการที่อนุญาตของแอปและเว็บไซต์ที่ได้รับอนุมัติ หากเซิร์ฟเวอร์ได้รับคําขอจากแอปหรือเว็บไซต์จากต้นทางที่ไม่รู้จัก ระบบควรปฏิเสธคําขอ

ในกรณีของเว็บ origin จะแสดงต้นทางในเว็บไซต์เดียวกันที่ลงชื่อเข้าใช้ข้อมูลเข้าสู่ระบบ ตัวอย่างเช่น เมื่อ URL คือ https://www.example.com:8443/store?category=shoes#athletic origin จะเท่ากับ https://www.example.com:8443

สําหรับแอป Android User Agent จะตั้งค่า origin เป็นลายเซ็นของแอปที่เรียกใช้โดยอัตโนมัติ คุณควรยืนยันลายเซ็นนี้ว่าตรงกันในเซิร์ฟเวอร์เพื่อตรวจสอบผู้เรียกใช้ Passkey API origin ของ Android คือ URI ที่ได้มาจากแฮช SHA-256 ของใบรับรองการลงนาม APK เช่น

android:apk-key-hash:<sha256_hash-of-apk-signing-cert>

คุณจะดูแฮช SHA-256 ของใบรับรองที่มีการรับรองจากคีย์สโตร์ได้โดยเรียกใช้คำสั่งเทอร์มินัลต่อไปนี้

keytool -list -keystore <path-to-apk-signing-keystore>

แฮช SHA-256 อยู่ในรูปแบบเลขฐานสิบหกที่คั่นด้วยโคลอน (91:F7:CB:F9:D6:81…) และค่า origin ของ Android จะอยู่ในรูปแบบ Base64url ตัวอย่าง Python นี้สาธิตวิธีแปลงรูปแบบแฮชเป็นรูปแบบฐานสิบหกที่คั่นด้วยโคลอนที่เข้ากันได้

import binascii
import base64
fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5'
print("android:apk-key-hash:" + base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))

แทนที่ค่าของ fingerprint ด้วยค่าของคุณเอง ตัวอย่างผลลัพธ์มีดังนี้

android:apk-key-hash:kffL-daBUxvHpY-4M8yhTavt5QnFEI2LsexohxrGPYU

จากนั้นคุณจะจับคู่สตริงดังกล่าวเป็นต้นทางที่อนุญาตบนเซิร์ฟเวอร์ได้ หากคุณมีใบรับรองการรับรองหลายรายการ เช่น ใบรับรองสำหรับการแก้ไขข้อบกพร่องและรุ่น หรือแอปหลายรายการ ให้ทำขั้นตอนนี้ซ้ำและยอมรับต้นทางทั้งหมดเหล่านั้นว่าถูกต้องในเซิร์ฟเวอร์

บันทึกรหัสผ่านของผู้ใช้

หากผู้ใช้ระบุชื่อผู้ใช้และรหัสผ่านสำหรับขั้นตอนการตรวจสอบสิทธิ์ในแอป คุณจะลงทะเบียนข้อมูลเข้าสู่ระบบของผู้ใช้ที่สามารถใช้ตรวจสอบสิทธิ์ผู้ใช้ได้ โดยสร้างออบเจ็กต์ CreatePasswordRequest ดังนี้

Kotlin

fun registerPassword(username: String, password: String) {
    // Initialize a CreatePasswordRequest object.
    val createPasswordRequest =
            CreatePasswordRequest(id = username, password = password)

    // Create credential and handle result.
    coroutineScope.launch {
        try {
            val result =
                credentialManager.createCredential(
                    // Use an activity based context to avoid undefined
                    // system UI launching behavior.
                    activityContext,
                    createPasswordRequest
                  )
            handleRegisterPasswordResult(result)
        } catch (e: CreateCredentialException) {
            handleFailure(e)
        }
    }
}

Java

void registerPassword(String username, String password) {
    // Initialize a CreatePasswordRequest object.
    CreatePasswordRequest createPasswordRequest =
        new CreatePasswordRequest(username, password);

    // Register the username and password.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined
        // system UI launching behavior
        requireActivity(),
        createPasswordRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                handleFailure(e);
            }
        }
    );
}

รองรับการกู้คืนข้อมูลเข้าสู่ระบบ

หากผู้ใช้เข้าถึงอุปกรณ์ที่จัดเก็บข้อมูลเข้าสู่ระบบไม่ได้แล้ว ผู้ใช้อาจต้องกู้คืนจากข้อมูลสำรองออนไลน์ที่ปลอดภัย หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีรองรับกระบวนการกู้คืนข้อมูลเข้าสู่ระบบนี้ โปรดอ่านส่วน "การกู้คืนสิทธิ์เข้าถึงหรือเพิ่มอุปกรณ์ใหม่" ในบล็อกโพสต์ความปลอดภัยของพาสคีย์ในเครื่องมือจัดการรหัสผ่านของ Google

เพิ่มการรองรับเครื่องมือจัดการรหัสผ่านที่มี URL ที่รู้จักของปลายทางพาสคีย์

เราขอแนะนำให้เพิ่มการรองรับ URL ที่รู้จักกันดีของปลายทางพาสคีย์สำหรับการผสานรวมที่ราบรื่นและความสามารถในความเข้ากันได้กับเครื่องมือการจัดการรหัสผ่านและข้อมูลเข้าสู่ระบบในอนาคต ซึ่งเป็นโปรโตคอลแบบเปิดสำหรับฝ่ายที่เกี่ยวข้องเพื่อโฆษณาการรองรับพาสคีย์อย่างเป็นทางการ รวมถึงระบุลิงก์โดยตรงสำหรับการลงทะเบียนและการจัดการพาสคีย์

  1. สำหรับผู้ใช้ใน https://example.com ซึ่งมีเว็บไซต์พร้อมแอป Android และ iOS URL ที่รู้จักกันดีจะเป็น https://example.com/.well-known/passkey-endpoints
  2. เมื่อมีการค้นหา URL การตอบกลับควรใช้สคีมาต่อไปนี้

    {
      "enroll": "https://example.com/account/manage/passkeys/create"
      "manage": "https://example.com/account/manage/passkeys"
    }
    
  3. หากต้องการให้ลิงก์นี้เปิดในแอปโดยตรงแทนที่จะเป็นในเว็บ ให้ใช้ลิงก์แอป Android

  4. ดูรายละเอียดเพิ่มเติมได้ในคำอธิบายURL ที่รู้จักกันดีของปลายทางพาสคีย์ใน GitHub

ช่วยให้ผู้ใช้จัดการพาสคีย์ได้โดยแสดงผู้ให้บริการที่สร้างพาสคีย์

ปัญหาหนึ่งที่ผู้ใช้พบเมื่อจัดการพาสคีย์หลายรายการที่เชื่อมโยงกับแอปหนึ่งๆ คือ การระบุพาสคีย์ที่ถูกต้องสำหรับการแก้ไขหรือลบ เพื่อช่วยแก้ปัญหานี้ เราขอแนะนำให้แอปและเว็บไซต์ระบุข้อมูลเพิ่มเติม เช่น ผู้ให้บริการที่สร้างข้อมูลเข้าสู่ระบบ วันที่สร้าง และวันที่ใช้ล่าสุดในรายการพาสคีย์ในหน้าจอการตั้งค่าของแอป โดยระบบจะดูข้อมูลผู้ให้บริการจาก AAGUID ที่เชื่อมโยงกับพาสคีย์ที่เกี่ยวข้อง AAGUID จะเป็นส่วนหนึ่งของข้อมูล Authenticator ของพาสคีย์

เช่น หากผู้ใช้สร้างพาสคีย์ในอุปกรณ์ที่ใช้ Android โดยใช้เครื่องมือจัดการรหัสผ่านบน Google นั้น RP จะได้รับ AAGUID ซึ่งมีลักษณะดังนี้ "ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4" ฝ่ายที่เชื่อถือสามารถกำกับเนื้อหาเพิ่มเติมในพาสคีย์ในรายการพาสคีย์เพื่อระบุว่าสร้างพาสคีย์โดยใช้เครื่องมือจัดการรหัสผ่านบน Google

หากต้องการแมป AAGUID กับผู้ให้บริการพาสคีย์ RP จะใช้ที่เก็บ AAGUID จากชุมชนได้ ค้นหา AAGUID ในรายการเพื่อดูชื่อและไอคอนของผู้ให้บริการพาสคีย์

ดูข้อมูลเพิ่มเติมเกี่ยวกับการผสานรวม AAGUID

แก้ปัญหาข้อผิดพลาดที่พบบ่อย

โปรดดูคู่มือการแก้ปัญหาเครื่องมือจัดการข้อมูลเข้าสู่ระบบเพื่อดูรหัสข้อผิดพลาดที่พบบ่อย คำอธิบาย และข้อมูลเกี่ยวกับสาเหตุ

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Credential Manager API และพาสคีย์ได้ที่แหล่งข้อมูลต่อไปนี้