Bagaimana KAYAK memangkas waktu login hingga 50% dan meningkatkan keamanan dengan kunci sandi

OKT 24, 2023
Kateryna Semenova Developer Relations Engineer, Android

Pendahuluan

KAYAK adalah salah satu mesin telusur perjalanan wisata terkemuka di dunia yang membantu pengguna menemukan penawaran terbaik untuk penerbangan, hotel, dan rental mobil. Pada tahun 2023, KAYAK mengintegrasikan kunci sandi - tipe baru autentikasi tanpa sandi - ke dalam aplikasi Android dan webnya. Hasilnya, KAYAK memangkas waktu rata-rata yang dibutuhkan pengguna untuk mendaftar dan login hingga 50%, dan mengalami penurunan tiket bantuan.

Studi kasus ini menjelaskan implementasi KAYAK di Android dengan Credential Manager API dan RxJava. Anda bisa menggunakan studi kasus ini sebagai model penerapan Credential Manager untuk meningkatkan keamanan dan pengalaman pengguna di aplikasi Anda.

Jika Anda menginginkan ringkasan singkat, lihat video pendukung di YouTube.

Link to Youtube Video (visible only when JS is disabled)

Masalah

Seperti kebanyakan bisnis, KAYAK mengandalkan sandi di masa lalu untuk mengautentikasi pengguna. Sandi adalah sebuah liabilitas bagi pengguna dan bisnis: mereka sering kali lemah, digunakan ulang, mudah ditebak, bisa di-phishing, dibocorkan, atau diretas.

“Penawaran autentikasi sandi membutuhkan banyak usaha dan risiko bagi bisnis. Penyerang terus-menerus mencoba membobol akun secara paksa, padahal tidak semua pengguna memahami perlunya sandi yang kuat. Namun, sandi yang kuat pun tidak sepenuhnya aman dan masih bisa di-phishing.” – Matthias Keller, Chief Scientist and SVP, Technology di KAYAK

Agar autentikasi lebih aman, KAYAK mengirimkan "magic link" melalui email. Meskipun membantu dari sudut pandang keamanan, langkah ekstra ini menimbulkan lebih banyak ketidaknyamanan bagi pengguna karena mengharuskan pengguna untuk beralih ke aplikasi yang berbeda untuk menyelesaikan proses login. Langkah tambahan perlu diperkenalkan untuk mengurangi risiko serangan phishing.

Solusi

Aplikasi Android KAYAK sekarang menggunakan kunci sandi untuk pengalaman autentikasi yang lebih aman, mudah digunakan, dan cepat. Kunci sandi adalah token unik dan aman yang disimpan di perangkat pengguna dan bisa disinkronkan di beberapa perangkat. Pengguna dapat login ke KAYAK menggunakan kunci sandi hanya dengan menggunakan kunci layar perangkat, sehingga lebih mudah dan aman daripada memasukkan sandi.

“Kami telah menambahkan dukungan kunci sandi ke aplikasi Android sehingga lebih banyak pengguna bisa menggunakan kunci sandi sebagai pengganti kata sandi. Dalam pekerjaan tersebut, kami juga mengganti implementasi Smartlock API yang lama dengan Login dengan Google yang didukung Credential Manager API. Sekarang, pengguna dapat mendaftar dan login ke KAYAK dengan kunci sandi dua kali lebih cepat dibandingkan dengan link email, sehingga meningkatkan rasio penyelesaian." – Matthias Keller, Chief Scientist and SVP, Technology di KAYAK

Integrasi Credential Manager API

Untuk mengintegrasikan kunci sandi di Android, KAYAK menggunakan Credential Manager API. Credential Manager adalah library Jetpack yang menyatukan dukungan kunci sandi mulai dari Android 9 (API level 28) dan dukungan untuk metode login tradisional seperti sandi dan autentikasi gabungan ke dalam satu antarmuka pengguna dan API.

kayak2
Gambar 1: Layar pembuatan kunci sandi Credential Manager.

Merancang alur autentikasi yang kuat untuk aplikasi sangatlah penting untuk memastikan keamanan dan pengalaman pengguna tepercaya. Diagram berikut menunjukkan bagaimana KAYAK mengintegrasikan kunci sandi ke dalam alur pendaftaran dan autentikasi mereka:

kayak3
Gambar 2: Diagram KAYAK yang menunjukkan alur pendaftaran dan autentikasi mereka.

Pada saat pendaftaran, pengguna diberi kesempatan untuk membuat kunci sandi. Setelah terdaftar, pengguna bisa masuk menggunakan kunci sandi, Login dengan Google, atau kata sandi. Karena Credential Manager meluncurkan UI secara otomatis, berhati-hatilah agar tidak menyebabkan waktu tunggu yang tidak terduga, seperti panggilan jaringan. Selalu ambil tantangan satu kali dan konfigurasi kunci sandi lainnya (seperti RP ID) di awal setiap sesi aplikasi.

Meskipun sekarang tim KAYAK banyak berinvestasi dalam coroutine, awalnya mereka menggunakan RxJava untuk terintegrasi dengan Credential Manager API. Mereka membungkus panggilan Credential Manager ke dalam RxJava sebagai berikut:

override fun createCredential(request: CreateCredentialRequest, activity: Activity): Single<CreateCredentialResponse> {
   return Single.create { emitter ->
       // Triggers credential creation flow
       credentialManager.createCredentialAsync(
           request = request,
           activity = activity,
           cancellationSignal = null,
           executor = Executors.newSingleThreadExecutor(),
           callback = object : CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> {
               override fun onResult(result: CreateCredentialResponse) {
                   emitter.onSuccess(result)
               }
 
               override fun onError(e: CreateCredentialException) {
                   emitter.tryOnError(e)
               }
           }
       )
   }
}

Contoh ini menjelaskan fungsi Kotlin yang disebut createCredential() yang menampilkan kredensial dari pengguna sebagai RxJava Single bertipe CreateCredentialResponse. Fungsi createCredential() mengenkapsulasi proses asinkron dari pendaftaran kredensial dalam gaya pemrograman reaktif menggunakan class RxJava Single.

Untuk proses implementasi Kotlin ini menggunakan coroutine, baca panduan Membuat pengguna login dengan Credential Manager.

Alur masuk pendaftaran pengguna baru

Contoh ini menunjukkan pendekatan yang digunakan KAYAK untuk mendaftarkan kredensial baru, di sini Credential Manager dibungkus dengan primitif Rx.

webAuthnRetrofitService
  .getClientParams(username = /** email address **/)
  .flatMap { response ->
      // Produce a passkeys request from client params that include a one-time challenge
      CreatePublicKeyCredentialOption(/** produce JSON from response **/)
  }
  .subscribeOn(schedulers.io())
  .flatMap { request ->
      // Call the earlier defined wrapper which calls the Credential Manager UI
      // to register a new passkey credential
      credentialManagerRepository
      .createCredential(
           request = request,
           activity = activity
       )
  }
  .flatMap {
     // send credential to the authentication server
  }
  .observeOn(schedulers.main())
  .subscribe(
      { /** process successful login, update UI etc. **/ },
      { /** process error, send to logger **/ }
  )

Rx memungkinkan KAYAK membuat pipeline yang lebih kompleks yang dapat melibatkan banyak interaksi dengan Credential Manager.

Login pengguna lama

KAYAK menggunakan langkah berikut untuk meluncurkan alur login. Proses ini meluncurkan elemen UI sheet bawah, yang memungkinkan pengguna login menggunakan ID Google dan kunci sandi atau kata sandi yang tersimpan.

kayak4
Gambar 3: Sheet bawah untuk autentikasi kunci sandi.

Developer harus mengikuti langkah-langkah berikut saat menyiapkan alur login:

  1. Karena sheet bawah diluncurkan secara otomatis, berhati-hatilah agar tidak menyebabkan waktu tunggu yang tidak terduga di UI, seperti panggilan jaringan. Selalu ambil tantangan satu kali dan konfigurasi kunci sandi lainnya (seperti RP ID) di awal setiap sesi aplikasi.
  2. Ketika menawarkan login Google melalui Credential Manager API, pertama-tama kode Anda harus mencari akun Google yang sudah pernah digunakan dengan aplikasi. Untuk menangani ini, panggil API dengan parameter setFilterByAuthorizedAccounts yang disetel ke true.
  3. Jika hasilnya menunjukkan daftar kredensial yang tersedia, aplikasi akan menampilkan UI autentikasi sheet bawah kepada pengguna.
  4. Jika muncul NoCredentialException, tidak ada kredensial yang ditemukan: Tidak ada akun Google, tidak ada kunci sandi, dan tidak ada kata sandi yang disimpan. Pada titik ini, aplikasi Anda harus memanggil API lagi dan menyetel setFilterByAuthorizedAccounts ke false untuk memulai alur Mendaftar dengan Google.
  5. Memproses kredensial yang dikembalikan dari Credential Manager.
Single.fromSupplier<GetPublicKeyCredentialOption> {
  GetPublicKeyCredentialOption(/** Insert challenge and RP ID that was fetched earlier **/)
}
  .flatMap { response ->
      // Produce a passkeys request
      GetPublicKeyCredentialOption(response.toGetPublicKeyCredentialOptionRequest())
  }
  .subscribeOn(schedulers.io())
  .map {  publicKeyCredentialOption ->
    // Merge passkeys request together with other desired options,
    // such as Google sign-in and saved passwords.
  }
  .flatMap { request ->
    // Trigger Credential Manager system UI
    credentialManagerRepository.getCredential(
      request = request,
      activity = activity
    )
  }
  .onErrorResumeNext { throwable ->
      // When offering Google sign-in, it is recommended to first only look for Google accounts
      // that have already been used with our app. If there are no such Google accounts, no passkeys,
      // and no saved passwords, we try looking for any Google sign-in one more time.
      if (throwable is NoCredentialException) {
        return@onErrorResumeNext credentialManagerRepository.getCredential(
          request = GetCredentialRequest(/* Google ID with filterByAuthorizedOnly = false */),
          activity = activity
        )
      }
    Single.error(throwable)
  }
  .flatMapCompletable {
    // Step 1: Use Retrofit service to send the credential to the server for validation. Waiting
    // for the server is handled on a IO thread using subscribeOn(schedulers.io()).
    // Step 2: Show the result in the UI. This includes changes such as loading the profile
    // picture, updating to the personalized greeting, making member-only areas active,
    // hiding the sign-in dialog, etc. The activities of step 2 are executed on the main thread.
  }
  .observeOn(schedulers.main())
  .subscribe(
    // Handle errors, e.g. send to log ingestion service. 
    // A subset of exceptions shown to the user can also be helpful,
    // such as user setup problems. 
    // Check out more info in Troubleshoot common errors at
    // https://developer.android.com/training/sign-in/passkeys#troubleshoot
  )
“Setelah Credential Manager API diimplementasikan secara umum, menambahkan metode autentikasi lainnya sangatlah mudah. Menambahkan Google One-Tap Sign In nyaris tanpa upaya setelah menambahkan kunci sandi.” – Matthias Keller

Untuk mempelajari lebih lanjut, ikuti panduan tentang cara Mengintegrasikan Credentials Manager API dan cara Mengintegrasikan Credential Manager dengan Login dengan Google.

Pertimbangan UX

Beberapa pertimbangan pengalaman pengguna utama yang dihadapi KAYAK ketika beralih ke kunci sandi termasuk apakah pengguna sebaiknya dapat menghapus kunci sandi atau membuat lebih dari satu kunci sandi.

Panduan UX kami untuk kunci sandi menyarankan agar Anda memiliki opsi untuk mencabut kunci sandi, dan memastikan bahwa pengguna tidak membuat kunci sandi duplikat untuk nama pengguna yang sama pada pengelola sandi yang sama.

kayak5
Gambar 4: UI KAYAK untuk manajemen kunci sandi.

Untuk mencegah pendaftaran beberapa kredensial untuk akun yang sama, KAYAK menggunakan properti excludeCredentials yang berisi daftar kredensial yang sudah terdaftar untuk pengguna. Contoh berikut menunjukkan cara membuat kredensial baru di Android tanpa membuat duplikat:

fun WebAuthnClientParamsResponse.toCreateCredentialRequest(): String {
        val credentialRequest = WebAuthnCreateCredentialRequest(
            challenge = this.challenge!!.asSafeBase64,
            relayingParty = this.relayingParty!!,
            pubKeyCredParams = this.pubKeyCredParams!!,
            userEntity = WebAuthnUserEntity(
              id = this.userEntity!!.id.asSafeBase64,
                   name = this.userEntity.name,
                   displayName = this.userEntity.displayName
            ),
            authenticatorSelection = WebAuthnAuthenticatorSelection(
                  authenticatorAttachment = "platform",
                  residentKey = "preferred"
             ),
            // Setting already existing credentials here prevents
            // creating multiple passkeys on the same keychain/password manager
            excludeCredentials = this.allowedCredentials!!.map { it.copy(id = it.id.asSafeBase64) },
        )
        return GsonBuilder().disableHtmlEscaping().create().toJson(credentialRequest)
}

Dan beginilah cara KAYAK mengimplementasikan fungsionalitas excludeCredentials untuk implementasi Web mereka.

var registrationOptions = {
   'publicKey': {
       'challenge': self.base64ToArrayBuffer(data.challenge),
       'rp': data.rp,
       'user': {
           'id': new TextEncoder().encode(data.user.id),
           'name': data.user.name,
           'displayName': data.user.displayName
       },
       'pubKeyCredParams': data.pubKeyCredParams,
       'authenticatorSelection': {
           'residentKey': 'required'
       }
   }
};
 
if (data.allowCredentials && data.allowCredentials.length > 0) {
   var excludeCredentials = [];
   for (var i = 0; i < data.allowCredentials.length; i++) {
       excludeCredentials.push({
           'id': self.base64ToArrayBuffer(data.allowCredentials[i].id),
           'type': data.allowCredentials[i].type
       });
   }
   registrationOptions.publicKey.excludeCredentials = excludeCredentials;
}
 
navigator.credentials.create(registrationOptions);

Implementasi sisi server

Bagian sisi server adalah komponen penting dari solusi autentikasi. KAYAK menambahkan kemampuan kunci sandi ke backend autentikasi mereka dengan memanfaatkan WebAuthn4J, sebuah library Java open source.

KAYAK menguraikan proses sisi server tersebut ke dalam beberapa langkah berikut:

  1. Klien meminta parameter yang diperlukan untuk membuat atau menggunakan kunci sandi dari server. Ini termasuk tantangan, algoritme enkripsi yang didukung, ID pihak yang mengandalkan, dan item terkait. Jika klien sudah memiliki alamat email pengguna, parameternya akan mencakup objek pengguna untuk pendaftaran, dan daftar kunci sandi jika ada.
  2. Klien menjalankan browser atau alur aplikasi untuk memulai pendaftaran kunci sandi atau melakukan login.
  3. Klien mengirimkan informasi kredensial yang diambil ke server. Ini termasuk ID klien, data pengautentikasi, data klien, dan item terkait lainnya. Informasi ini diperlukan untuk membuat akun atau memverifikasi login.

Ketika KAYAK mengerjakan proyek ini, tidak ada produk pihak ketiga yang mendukung kunci sandi. Namun, sekarang banyak referensi yang tersedia untuk membuat server kunci sandi, termasuk dokumentasi dan contoh library.

Hasil

Sejak mengintegrasikan kunci sandi, KAYAK melihat peningkatan yang signifikan dalam hal kepuasan pengguna. Pengguna melaporkan bahwa mereka merasa kunci sandi jauh lebih mudah digunakan daripada kata sandi, karena tidak mengharuskan pengguna untuk mengingat atau mengetik rangkaian karakter yang panjang dan rumit. KAYAK mengurangi waktu rata-rata yang dibutuhkan pengguna untuk mendaftar dan login sebesar 50%, menurunkan jumlah tiket bantuan yang terkait dengan kelupaan kata sandi, dan membuat sistem lebih aman dengan mengurangi eksposur terhadap serangan berbasis kata sandi. Berkat peningkatan ini, KAYAK berencana untuk menghilangkan autentikasi berbasis kata sandi di aplikasi mereka pada akhir tahun 2023.

“Kunci sandi mempercepat pembuatan akun karena tidak perlu lagi membuat kata sandi atau membuka aplikasi terpisah untuk mendapatkan link atau kode. Sebagai bonus, implementasi library Credential Manager yang baru juga mengurangi beban teknis dalam code base kami dengan memasukkan kunci sandi, kata sandi, dan login Google ke dalam satu UI modern yang baru. Bahkan, pengguna bisa mendaftar dan login ke KAYAK dengan kunci sandi dua kali lebih cepat dibandingkan dengan link email, yang juga meningkatkan rasio penyelesaian." – Matthias Keller

Kesimpulan

Kunci sandi adalah solusi autentikasi baru dan inovatif yang menawarkan manfaat signifikan dibandingkan kata sandi tradisional. KAYAK adalah contoh yang bagus tentang bagaimana sebuah organisasi bisa meningkatkan keamanan dan kegunaan proses autentikasi dengan mengintegrasikan kunci sandi. Jika Anda mencari pengalaman autentikasi yang lebih aman dan mudah digunakan, kami sarankan Anda untuk mempertimbangkan penggunaan kunci sandi dengan Credential Manager API Android.