FIDO2 API Android Pertama Anda

1. Pengantar

Apa itu FIDO2 API?

FIDO2 API memungkinkan aplikasi Android membuat dan menggunakan kredensial berbasis kunci publik yang kuat dan disahkan untuk tujuan mengautentikasi pengguna. API ini menyediakan implementasi Klien WebAuthn, yang mendukung penggunaan pengautentikasi BLE, NFC, dan roaming USB (kunci keamanan) serta pengautentikasi platform, yang memungkinkan pengguna melakukan autentikasi menggunakan sidik jari atau kunci layar.

Yang akan Anda build...

Dalam codelab ini, Anda akan mem-build aplikasi Android dengan fungsi autentikasi ulang sederhana menggunakan sensor sidik jari. "Autentikasi ulang" adalah saat pengguna login ke aplikasi, lalu melakukan autentikasi ulang saat mereka beralih kembali ke aplikasi Anda, atau saat mencoba mengakses bagian penting aplikasi Anda. Kasus yang terakhir ini juga disebut sebagai "autentikasi langkah-langkah".

Yang akan Anda pelajari ...

Anda akan mempelajari cara memanggil Android FIDO2 API dan opsi yang dapat Anda berikan agar dapat digunakan untuk berbagai situasi. Anda juga akan mempelajari praktik terbaik khusus autentikasi ulang.

Yang akan Anda butuhkan ...

  • Perangkat Android dengan sensor sidik jari (meskipun tanpa sensor sidik jari, kunci layar dapat memberikan fungsi verifikasi pengguna yang setara)
  • Android OS 7.0 atau yang lebih baru dengan update terbaru. Pastikan untuk mendaftarkan sidik jari (atau kunci layar).

2. Mempersiapkan

Membuat Clone Repositori

Lihat repositori GitHub.

https://github.com/android/codelab-fido2

$ git clone https://github.com/android/codelab-fido2.git

Apa yang akan kita terapkan?

  • Mengizinkan pengguna mendaftarkan "pengautentikasi platform yang memverifikasi pengguna" (ponsel Android yang memiliki sensor sidik jari akan bertindak sebagai satu perangkat).
  • Mengizinkan pengguna mengautentikasi ulang dirinya ke aplikasi menggunakan sidik jari mereka.

Anda dapat melihat pratinjau apa yang akan dibangun di sini.

Memulai project codelab Anda

Aplikasi yang sudah selesai akan mengirimkan permintaan ke server di https://webauthn-codelab.glitch.me. Anda dapat mencoba versi web dari aplikasi yang sama di sana.

c2234c42ba8a6ef1.png

Anda akan mengerjakan versi aplikasi Anda sendiri.

  1. Buka halaman edit situs di https://glitch.com/edit/#!/webauthn-codelab.
  2. Cari "Remix untuk Mengedit" di pojok kanan atas. Dengan menekan tombol, Anda dapat "{i>fork<i}" kode dan melanjutkan dengan versi Anda sendiri bersama dengan URL proyek baru. 9ef108869885e4ce.pngS
  3. Salin nama project di kiri atas (Anda dapat mengubahnya sesuai keinginan). c91d0d59c61021a4.png
  4. Tempel ke bagian HOSTNAME file .env di glitch. 889b55b1cf74b894.png

3. Mengaitkan aplikasi dan situs Anda dengan Digital Asset Links

Untuk menggunakan FIDO2 API di aplikasi Android, kaitkan dengan situs dan bagikan kredensial di antara keduanya. Untuk melakukannya, manfaatkan Digital Asset Links. Anda dapat mendeklarasikan pengaitan dengan menghosting file JSON Digital Asset Links di situs, dan menambahkan link ke file Digital Asset Link ke manifes aplikasi.

Menghosting .well-known/assetlinks.json di domain Anda

Anda dapat menentukan pengaitan antara aplikasi dan situs dengan membuat file JSON dan meletakkannya di .well-known/assetlinks.json. Untungnya, kita memiliki kode server yang menampilkan file assetlinks.json secara otomatis, hanya dengan menambahkan parameter lingkungan berikut ke file .env yang mengalami glitch:

  • ANDROID_PACKAGENAME: Nama paket aplikasi Anda (com.example.android.fido2)
  • ANDROID_SHA256HASH: SHA256 Hash sertifikat penandatanganan Anda

Untuk mendapatkan hash SHA256 sertifikat penandatanganan developer, gunakan perintah di bawah. Sandi default keystore debug adalah "android".

$ keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore

Dengan mengakses https://<your-project-name>.glitch.me/.well-known/assetlinks.json , Anda akan melihat string JSON seperti ini:

[{
  "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
  "target": {
    "namespace": "web",
    "site": "https://<your-project-name>.glitch.me"
  }
}, {
  "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.android.fido2",
    "sha256_cert_fingerprints": ["DE:AD:BE:EF:..."]
  }
}]

Membuka project di Android Studio

Klik "Open an existing Android Studio project" di layar sambutan Android Studio.

Pilih "android" folder di dalam repositori check out.

1062875cf11ffb95.png

Mengaitkan aplikasi dengan remix Anda

Buka file gradle.properties. Di bagian bawah file, ubah URL host ke remix Glitch yang baru saja Anda buat.

// ...

# The URL of the server
host=https://<your-project-name>.glitch.me

Pada tahap ini, konfigurasi Digital Asset Links Anda seharusnya sudah siap.

4. Lihat cara kerja aplikasi sekarang

Mari kita mulai dengan memeriksa cara kerja aplikasi tersebut sekarang. Pastikan untuk memilih "app-start" di combobox konfigurasi run. Klik "Jalankan" (segitiga hijau di samping combobox) untuk meluncurkan aplikasi di perangkat Android yang terhubung.

29351fb97062b43c.pngS

Saat meluncurkan aplikasi, Anda akan melihat layar untuk mengetik nama pengguna. Ini adalah UsernameFragment. Untuk tujuan demonstrasi, aplikasi dan server menerima nama pengguna apa pun. Cukup ketik sesuatu, lalu tekan "Next".

bd9007614a9a3644.png

Layar berikutnya yang Anda lihat adalah AuthFragment. Di sini, pengguna dapat login dengan sandi. Nantinya, kita akan menambahkan fitur login dengan FIDO2 di sini. Sekali lagi, untuk tujuan demonstrasi, aplikasi dan server akan menerima sandi apa pun. Cukup ketik sesuatu dan tekan "Login".

d9caba817a0a99bd.png

Ini adalah layar terakhir aplikasi ini, HomeFragment. Untuk saat ini, Anda hanya melihat daftar kredensial kosong di sini. Menekan "Reauth" membawa Anda kembali ke AuthFragment. Menekan "Logout" membawa Anda kembali ke UsernameFragment. Tombol tindakan mengambang dengan "+" tidak melakukan apa-apa sekarang, tapi itu akan memulai pendaftaran sebuah

kredensial baru setelah Anda menerapkan alur pendaftaran FIDO2.

1cfcc6c884020e37.png

Sebelum mulai membuat kode, berikut teknik yang berguna. Di Android Studio, tekan "TODO" di bagian bawah. Tindakan ini akan menampilkan daftar semua TODO dalam codelab ini. Kita akan mulai dengan TODO pertama di bagian berikutnya.

e5a811bbc7cd7b30.png

5. Mendaftarkan kredensial menggunakan sidik jari

Untuk mengaktifkan autentikasi menggunakan sidik jari, pertama-tama Anda harus mendaftarkan kredensial yang dibuat oleh pengguna yang memverifikasi pengautentikasi platform - pengautentikasi yang disematkan di perangkat dan memverifikasi pengguna menggunakan biometrik, seperti sensor sidik jari.

37ce78fdf2759832.pngS

Seperti yang telah kita lihat di bagian sebelumnya, tombol aksi mengambang sekarang tidak melakukan apa-apa. Mari kita lihat cara mendaftarkan kredensial baru.

Memanggil API server: /auth/registerRequest

Buka AuthRepository.kt dan temukan TODO(1).

Di sini, registerRequest adalah metode yang dipanggil saat FAB ditekan. Kita akan membuat metode ini memanggil API server /auth/registerRequest. API menampilkan ApiResult dengan semua PublicKeyCredentialCreationOptions yang diperlukan klien untuk membuat kredensial baru.

Selanjutnya, kita dapat memanggil getRegisterPendingIntent dengan opsi yang ada. FIDO2 API ini menampilkan PendingIntent Android untuk membuka dialog sidik jari dan menghasilkan kredensial baru, dan kita dapat mengembalikan PendingIntent tersebut ke pemanggil.

Metode akan terlihat seperti di bawah ini.

suspend fun registerRequest(): PendingIntent? {
  fido2ApiClient?.let { client ->
    try {
      val sessionId = dataStore.read(SESSION_ID)!!
      when (val apiResult = api.registerRequest(sessionId)) {
        ApiResult.SignedOutFromServer -> forceSignOut()
        is ApiResult.Success -> {
          if (apiResult.sessionId != null) {
            dataStore.edit { prefs ->
              prefs[SESSION_ID] = apiResult.sessionId
            }
          }
          val task = client.getRegisterPendingIntent(apiResult.data)
          return task.await()
        }
      }
    } catch (e: Exception) {
      Log.e(TAG, "Cannot call registerRequest", e)
    }
  }
  return null
}

Membuka dialog sidik jari untuk pendaftaran

Buka HomeFragment.kt dan temukan TODO(2).

Di sinilah UI mendapatkan Intent kembali dari AuthRepository. Di sini, kita akan menggunakan anggota createCredentialIntentLauncher untuk meluncurkan PendingIntent yang kita dapatkan sebagai hasil dari langkah sebelumnya. Tindakan ini akan membuka dialog untuk pembuatan kredensial.

binding.add.setOnClickListener {
  lifecycleScope.launch {
    val intent = viewModel.registerRequest()
    if (intent != null) {
      createCredentialIntentLauncher.launch(
        IntentSenderRequest.Builder(intent).build()
      )
    }
  }
}

Menerima ActivityResult dengan Kredensial baru

Buka HomeFragment.kt dan temukan TODO(3).

Metode handleCreateCredentialResult ini dipanggil setelah dialog sidik jari ditutup. Jika kredensial berhasil dibuat, anggota data dari ActivityResult akan berisi informasi kredensial.

Pertama, kita harus mengekstrak PublicKeyCredential dari data. Intent data memiliki kolom tambahan array byte dengan kunci Fido.FIDO2_KEY_CREDENTIAL_EXTRA. Anda dapat menggunakan metode statis di PublicKeyCredential yang disebut deserializeFromBytes untuk mengubah array byte menjadi objek PublicKeyCredential.

Selanjutnya, periksa apakah anggota response objek kredensial ini adalah AuthenticationErrorResponse. Jika ya, maka terjadi error saat membuat kredensial; jika tidak, kita bisa mengirim kredensial ke backend.

Metode yang sudah selesai akan terlihat seperti ini:

private fun handleCreateCredentialResult(activityResult: ActivityResult) {
  val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
  when {
    activityResult.resultCode != Activity.RESULT_OK ->
      Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_LONG).show()
    bytes == null ->
      Toast.makeText(requireContext(), R.string.credential_error, Toast.LENGTH_LONG)
        .show()
    else -> {
      val credential = PublicKeyCredential.deserializeFromBytes(bytes)
      val response = credential.response
      if (response is AuthenticatorErrorResponse) {
        Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_LONG)
          .show()
      } else {
        viewModel.registerResponse(credential)
      }
    }
  }
}

Memanggil API server: /auth/registerResponse

Buka AuthRepository.kt dan temukan TODO(4).

Metode registerResponse ini dipanggil setelah UI berhasil membuat kredensial baru, dan kita ingin mengirimkannya kembali ke server.

Objek PublicKeyCredential memiliki informasi tentang kredensial yang baru dibuat di dalamnya. Kita sekarang ingin mengingat ID kunci lokal sehingga kita dapat membedakannya dari kunci lain yang terdaftar di server. Di objek PublicKeyCredential, ambil properti rawId dan masukkan dalam variabel string lokal menggunakan toBase64.

Sekarang kita siap untuk mengirim informasi ke server. Gunakan api.registerResponse untuk memanggil API server dan mengirim kembali respons. Nilai yang ditampilkan berisi daftar semua kredensial yang terdaftar di server, termasuk yang baru.

Terakhir, kita dapat menyimpan hasilnya di DataStore. Daftar kredensial harus disimpan dengan kunci CREDENTIALS sebagai StringSet. Anda dapat menggunakan toStringSet untuk mengonversi daftar kredensial menjadi StringSet.

Selain itu, kita menyimpan ID kredensial dengan kunci LOCAL_CREDENTIAL_ID.

suspend fun registerResponse(credential: PublicKeyCredential) {
  try {
    val sessionId = dataStore.read(SESSION_ID)!!
    val credentialId = credential.rawId.toBase64()
    when (val result = api.registerResponse(sessionId, credential)) {
      ApiResult.SignedOutFromServer -> forceSignOut()
      is ApiResult.Success -> {
        dataStore.edit { prefs ->
          result.sessionId?.let { prefs[SESSION_ID] = it }
          prefs[CREDENTIALS] = result.data.toStringSet()
          prefs[LOCAL_CREDENTIAL_ID] = credentialId
        }
      }
    }
  } catch (e: ApiException) {
    Log.e(TAG, "Cannot call registerResponse", e)
  }
}

Jalankan aplikasi, dan Anda akan dapat mengklik FAB dan mendaftarkan kredensial baru.

7d64d9289c5a3cbc.pngS

6. Mengautentikasi pengguna dengan sidik jari

Kita kini memiliki kredensial yang terdaftar di aplikasi dan server. Sekarang kita dapat menggunakannya untuk mengizinkan pengguna login. Kami menambahkan fitur login dengan sidik jari ke AuthFragment. Saat pengguna membukanya, dialog sidik jari akan ditampilkan. Saat autentikasi berhasil, pengguna akan dialihkan ke HomeFragment.

Memanggil API server: /auth/signinRequest

Buka AuthRepository.kt dan temukan TODO(5).

Metode signinRequest ini dipanggil saat AuthFragment dibuka. Di sini, kita ingin meminta server dan melihat apakah kita dapat mengizinkan pengguna login dengan FIDO2.

Pertama, kita harus mengambil PublicKeyCredentialRequestOptions dari server. Gunakan api.signInRequest untuk memanggil API server. ApiResult yang ditampilkan berisi PublicKeyCredentialRequestOptions.

Dengan PublicKeyCredentialRequestOptions, kita dapat menggunakan getSignIntent FIDO2 API untuk membuat PendingIntent guna membuka dialog sidik jari.

Terakhir, kita dapat mengembalikan PendingIntent kembali ke UI.

suspend fun signinRequest(): PendingIntent? {
  fido2ApiClient?.let { client ->
    val sessionId = dataStore.read(SESSION_ID)!!
    val credentialId = dataStore.read(LOCAL_CREDENTIAL_ID)
    if (credentialId != null) {
      when (val apiResult = api.signinRequest(sessionId, credentialId)) {
        ApiResult.SignedOutFromServer -> forceSignOut()
        is ApiResult.Success -> {
          val task = client.getSignPendingIntent(apiResult.data)
          return task.await()
        }
      }
    }
  }
  return null
}

Membuka dialog sidik jari untuk pernyataan

Buka AuthFragment.kt dan temukan TODO(6).

Ini hampir sama dengan apa yang kita lakukan untuk pendaftaran. Kita dapat meluncurkan dialog sidik jari dengan anggota signIntentLauncher.

viewLifecycleOwner.lifecycleScope.launchWhenStarted {
  launch {
    viewModel.signinRequests.collect { intent ->
      signIntentLauncher.launch(
        IntentSenderRequest.Builder(intent).build()
      )
    }
  }
  launch {
    ...
  }
}

Menangani ActivityResult

Buka AuthFragment.kt dan cari TODO(7).

Sekali lagi, ini sama dengan apa yang kami lakukan untuk pendaftaran. Kita dapat mengekstrak PublicKeyCredential, memeriksa error, dan meneruskannya ke ViewModel.

private fun handleSignResult(activityResult: ActivityResult) {
  val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
  when {
    activityResult.resultCode != Activity.RESULT_OK ->
      Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_SHORT).show()
    bytes == null ->
      Toast.makeText(requireContext(), R.string.auth_error, Toast.LENGTH_SHORT)
        .show()
    else -> {
      val credential = PublicKeyCredential.deserializeFromBytes(bytes)
      val response = credential.response
      if (response is AuthenticatorErrorResponse) {
        Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_SHORT)
          .show()
      } else {
        viewModel.signinResponse(credential)
      }
    }
  }
}

Memanggil API server: /auth/signinResponse

Buka AuthRepository.kt dan temukan TODO(8).

Objek PublicKeyCredential memiliki ID kredensial di dalamnya sebagai keyHandle. Sama seperti yang kita lakukan dalam alur pendaftaran, mari kita simpan ini dalam variabel string lokal sehingga kita dapat menyimpannya nanti.

Sekarang kita siap memanggil API server dengan api.signinResponse. Nilai yang ditampilkan berisi daftar kredensial.

Pada tahap ini, proses login berhasil. Kita harus menyimpan semua hasil di DataStore. Daftar kredensial harus disimpan sebagai StringSet dengan kunci CREDENTIALS. ID kredensial lokal yang kita simpan di atas harus disimpan sebagai string dengan kunci LOCAL_CREDENTIAL_ID.

Terakhir, kita perlu memperbarui status login sehingga UI dapat mengalihkan pengguna ke HomeFragment. Hal ini dapat dilakukan dengan memunculkan objek SignInState.SignedIn ke SharedFlow bernama signInStateMutable. Kita juga ingin memanggil refreshCredentials untuk mengambil kredensial pengguna sehingga pengguna tersebut akan tercantum di UI.

suspend fun signinResponse(credential: PublicKeyCredential) {
  try {
    val username = dataStore.read(USERNAME)!!
    val sessionId = dataStore.read(SESSION_ID)!!
    val credentialId = credential.rawId.toBase64()
    when (val result = api.signinResponse(sessionId, credential)) {
      ApiResult.SignedOutFromServer -> forceSignOut()
      is ApiResult.Success -> {
        dataStore.edit { prefs ->
          result.sessionId?.let { prefs[SESSION_ID] = it }
          prefs[CREDENTIALS] = result.data.toStringSet()
          prefs[LOCAL_CREDENTIAL_ID] = credentialId
        }
        signInStateMutable.emit(SignInState.SignedIn(username))
        refreshCredentials()
      }
    }
  } catch (e: ApiException) {
    Log.e(TAG, "Cannot call registerResponse", e)
  }
}

Jalankan aplikasi dan klik "Reauth" untuk membuka AuthFragment. Sekarang Anda akan melihat dialog sidik jari yang meminta Anda untuk login dengan sidik jari.

45f81419f84952c8.pngS

Selamat! Anda kini telah mempelajari cara menggunakan FIDO2 API di Android untuk pendaftaran dan login.

7. Selamat!

Anda berhasil menyelesaikan codelab - Android FIDO2 API pertama Anda.

Yang telah Anda pelajari

  • Cara mendaftarkan kredensial menggunakan pengautentikasi platform yang memverifikasi pengguna.
  • Cara mengautentikasi pengguna menggunakan pengautentikasi terdaftar.
  • Opsi yang tersedia untuk mendaftarkan pengautentikasi baru.
  • Praktik terbaik UX untuk autentikasi ulang menggunakan sensor biometrik.

Langkah berikutnya

  • Pelajari cara membangun pengalaman serupa di situs.

Anda dapat mempelajarinya dengan mencoba codelab WebAuthn pertama Anda.

Resource

Terima kasih banyak kepada Yuriy Ackermann dari FIDO Alliance atas bantuan Anda.