Section 8/171 menit

8. Sync Multi-Device via iCloud Keychain

8. Sync Multi-Device via iCloud Keychain

Passkeys yang terdaftar di satu perangkat Apple otomatis tersedia di perangkat lain melalui iCloud Keychain — tanpa action user tambahan. Ini adalah keunggulan besar dibanding hardware security key yang hanya bisa dipakai di satu perangkat.

Cross-Device Authentication (iOS 17+)

Untuk login di perangkat lain (misalnya laptop Windows atau Android) menggunakan passkey di iPhone:

swift
// iOS 17+: support untuk cross-platform passkey (CTAP via Bluetooth)
// Ketika user memilih "Use passkey from nearby device":
// 1. QR code muncul di browser/device lain
// 2. iPhone scan QR code via kamera
// 3. Bluetooth proximity diverifikasi (keamanan)
// 4. Face ID / Touch ID di iPhone
// 5. Login berhasil di browser/device lain

// Di app iOS, support ini otomatis — tidak perlu kode tambahan
// System menangani cross-device flow via ASAuthorizationController

Mengelola Daftar Passkey

swift
// Menampilkan passkey yang terdaftar (iOS 18+)
// Sebelum iOS 18: gunakan keychain query langsung

@MainActor
final class PasskeyManagementViewModel: ObservableObject {
    @Published var registeredPasskeys: [RegisteredPasskey] = []

    struct RegisteredPasskey: Identifiable {
        let id: Data      // credentialID
        let displayName: String
        let registeredAt: Date
        let lastUsed: Date?
    }

    func loadRegisteredPasskeys() async throws {
        // Fetch dari server: server menyimpan daftar credential ID + metadata
        let credentials = try await AuthAPIClient.shared.listPasskeys()
        registeredPasskeys = credentials.map {
            RegisteredPasskey(
                id: Data(base64URLEncoded: $0.credentialID)!,
                displayName: $0.deviceName ?? "iPhone",
                registeredAt: $0.createdAt,
                lastUsed: $0.lastUsedAt
            )
        }
    }

    func deletePasskey(id: Data) async throws {
        try await AuthAPIClient.shared.deletePasskey(
            credentialID: id.base64URLEncodedString()
        )
        registeredPasskeys.removeAll { $0.id == id }
    }
}