Mempelajari cara menyederhanakan perjalanan autentikasi menggunakan Credential Manager API di aplikasi Android

1. Sebelum memulai

Solusi autentikasi tradisional menimbulkan sejumlah tantangan keamanan dan kegunaan.

Sandi banyak digunakan, tetapi ...

  • Mudah dilupakan
  • Pengguna memerlukan pengetahuan untuk membuat sandi kuat.
  • Mudah di-phising, dicuri, dan di-replay oleh penyerang.

Android telah berupaya membuat Credential Manager API guna memudahkan pengalaman login dan mengatasi risiko keamanan dengan mendukung kunci sandi, yakni standar industri generasi berikutnya untuk autentikasi tanpa sandi.

Credential Manager menyatukan dukungan untuk kunci sandi dan menggabungkannya dengan metode autentikasi tradisional seperti sandi, Login dengan Google, dll.

Pengguna akan dapat membuat kunci sandi, menyimpannya di Pengelola Sandi Google, yang akan menyinkronkan kunci sandi tersebut di berbagai perangkat Android tempat pengguna login. Kunci sandi harus dibuat, dikaitkan dengan akun pengguna, dan disimpan kunci publiknya di server sebelum pengguna dapat menggunakannya untuk login.

Dalam codelab ini, Anda akan mempelajari cara mendaftar dengan kunci sandi dan sandi menggunakan Credential Manager API, serta mempelajari penggunaan kunci sandi dan sandi tersebut untuk tujuan autentikasi mendatang. Terdapat 2 alur yang mencakup:

  • Daftar: menggunakan kunci sandi dan sandi.
  • Login: menggunakan kunci sandi & sandi tersimpan.

Prasyarat

  • Pemahaman dasar tentang cara menjalankan aplikasi di Android Studio.
  • Pemahaman dasar tentang alur autentikasi di aplikasi Android.
  • Pemahaman dasar tentang kunci sandi.

Yang akan Anda pelajari

  • Cara membuat kunci sandi.
  • Cara menyimpan sandi di pengelola sandi.
  • Cara mengautentikasi pengguna dengan kunci sandi atau sandi tersimpan.

Yang akan Anda butuhkan

Salah satu dari kombinasi perangkat berikut:

  • Perangkat Android yang menjalankan Android 9 atau yang lebih tinggi (untuk kunci sandi) dan Android 4.4 atau yang lebih tinggi (untuk autentikasi sandi melalui Credential Manager API).
  • Perangkat sebaiknya memiliki sensor biometrik.
  • Pastikan untuk mendaftarkan biometrik (atau kunci layar).
  • Versi plugin Kotlin: 1.8.10

2. Memulai persiapan

  1. Clone repositori ini di laptop Anda dari cabang credman_codelab: https://github.com/android/identity-samples/tree/credman_codelab
  2. Buka modul CredentialManager, lalu buka project di Android Studio.

Mari kita lihat status awal aplikasi

Untuk melihat cara kerja status awal aplikasi, ikuti langkah-langkah berikut:

  1. Luncurkan aplikasi.
  2. Anda akan melihat layar utama dengan tombol daftar dan login.
  3. Anda dapat mengklik tombol daftar untuk mendaftar menggunakan kunci sandi atau sandi.
  4. Anda dapat mengklik tombol login untuk login menggunakan kunci sandi & sandi tersimpan.

8c0019ff9011950a.jpeg

Untuk memahami apa itu kunci sandi beserta cara kerjanya, lihat Bagaimana cara kerja kunci sandi? .

3. Menambahkan kemampuan untuk mendaftar menggunakan kunci sandi

Saat mendaftarkan akun baru di aplikasi Android yang menggunakan Credential Manager API, pengguna dapat membuat kunci sandi untuk akun mereka. Kunci sandi ini akan disimpan dengan aman di penyedia kredensial pilihan pengguna serta digunakan untuk login berikutnya, tanpa mengharuskan pengguna memasukkan sandi mereka setiap saat.

Sekarang, Anda akan membuat kunci sandi dan mendaftarkan kredensial pengguna menggunakan biometrik/kunci layar.

Daftar dengan kunci sandi

Di dalam Credential Manager -> app -> main -> java -> SignUpFragment.kt, Anda dapat melihat kolom teks "username" dan tombol untuk mendaftar dengan kunci sandi.

dcc5c529b310f2fb.jpeg

Teruskan verifikasi login dan respons json lainnya ke panggilan createPasskey()

Sebelum kunci sandi dibuat, Anda perlu meminta server untuk mendapatkan informasi yang diperlukan guna diteruskan ke Credential Manager API selama panggilan createCredential().

Untungnya, Anda sudah memiliki respons tiruan di aset Anda (RegFromServer.txt) yang menampilkan parameter tersebut dalam codelab ini.

  • Di aplikasi Anda, buka metode SignUpFragment.kt, Find, signUpWithPasskeys tempat Anda akan menulis logika untuk membuat kunci sandi dan memungkinkan pengguna masuk. Anda dapat menemukan metode tersebut di class yang sama.
  • Periksa blok else dengan komentar untuk memanggil createPasskey(), lalu ganti dengan kode berikut:

SignUpFragment.kt

//TODO : Call createPasskey() to signup with passkey

val data = createPasskey()

Metode ini akan dipanggil setelah Anda mengisi nama pengguna yang valid di layar.

  • Di dalam metode createPasskey(), Anda perlu membuat CreatePublicKeyCredentialRequest() dengan menampilkan parameter yang diperlukan.

SignUpFragment.kt

//TODO create a CreatePublicKeyCredentialRequest() with necessary registration json from server

val request = CreatePublicKeyCredentialRequest(fetchRegistrationJsonFromServer())

fetchRegistrationJsonFromServer() ini adalah metode, yang membaca respons json pendaftaran dari aset dan menampilkan json pendaftaran untuk diteruskan saat membuat kunci sandi.

  • Temukan metode fetchRegistrationJsonFromServer() dan ganti TODO dengan kode berikut untuk menampilkan json serta menghapus pernyataan return string yang kosong:

SignUpFragment.kt

//TODO fetch registration mock response

val response = requireContext().readFromAsset("RegFromServer")

//Update userId,challenge, name and Display name in the mock
return response.replace("<userId>", getEncodedUserId())
   .replace("<userName>", binding.username.text.toString())
   .replace("<userDisplayName>", binding.username.text.toString())
   .replace("<challenge>", getEncodedChallenge())
  • Di sini, Anda akan membaca json pendaftaran dari aset.
  • Json ini memiliki 4 kolom untuk diganti.
  • UserId harus unik sehingga pengguna dapat membuat beberapa kunci sandi (jika diperlukan). Anda akan mengganti <userId> dengan userId yang dihasilkan.
  • <challenge> juga harus unik sehingga Anda akan menghasilkan verifikasi login yang unik dan acak. Metode ini sudah ada dalam kode Anda.

Cuplikan kode berikut menyertakan opsi contoh yang Anda terima dari server:

{
  "challenge": String,
  "rp": {
    "name": String,
    "id": String
  },
  "user": {
    "id": String,
    "name": String,
    "displayName": String
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

Tabel berikut tidak lengkap, tetapi berisi parameter penting dalam kamus PublicKeyCredentialCreationOptions:

Parameter

Deskripsi

challenge

String acak yang dihasilkan server yang berisi cukup entropi sehingga tidak mungkin ditebak. Panjang string harus setidaknya 16 byte. String ini diperlukan, tetapi tidak akan digunakan selama pendaftaran kecuali jika melakukan pengesahan.

user.id

ID unik pengguna. Nilai ini tidak boleh menyertakan informasi identitas pribadi, misalnya alamat email atau nama pengguna. Sebaiknya gunakan nilai acak 16 byte yang dihasilkan per akun.

user.name

Kolom ini harus berisi ID unik untuk akun yang akan dikenali pengguna, seperti alamat email atau nama pengguna. ID unik ini akan ditampilkan di pemilih akun. (Jika menggunakan nama pengguna, gunakan nilai yang sama seperti pada autentikasi sandi.)

user.displayName

Kolom ini merupakan nama opsional yang lebih mudah digunakan untuk akun. Nama tersebut mudah dipahami untuk akun pengguna, dan hanya dimaksudkan sebagai tampilan.

rp.id

Entitas Pihak Tepercaya akan sesuai dengan detail aplikasi Anda. Entitas ini harus memiliki:

  • nama (wajib): nama aplikasi Anda
  • ID (opsional): sesuai dengan domain atau subdomain. Jika tidak ada, domain saat ini akan digunakan.
  • ikon (opsional).

pubKeyCredParams

Parameter Kredensial Kunci Publik adalah daftar algoritma dan jenis kunci yang diizinkan. Daftar ini harus berisi setidaknya satu elemen.

excludeCredentials

Pengguna yang mencoba mendaftarkan sebuah perangkat mungkin telah mendaftarkan perangkat lain. Untuk membatasi pembuatan beberapa kredensial untuk akun yang sama pada satu pengautentikasi, Anda dapat mengabaikan perangkat ini. Anggota transports, jika tersedia, harus berisi hasil pemanggilan getTransports() selama pendaftaran setiap kredensial.

authenticatorSelection.authenticatorAttachment

menunjukkan apakah perangkat harus terpasang ke platform atau tidak atau apakah tidak ada persyaratan mengenai hal tersebut. Tetapkan ke "platform". Hal ini menunjukkan bahwa kita ingin pengautentikasi disematkan ke perangkat platform, dan pengguna tidak akan diminta untuk memasukkan, mis. kunci keamanan USB.

residentKey

menunjukkan nilai "wajib" untuk membuat kunci sandi.

Buat kredensial

  1. Setelah membuat CreatePublicKeyCredentialRequest(), Anda perlu memanggil panggilan createCredential() dengan permintaan yang dibuat.

SignUpFragment.kt

//TODO call createCredential() with createPublicKeyCredentialRequest

try {
   response = credentialManager.createCredential(
       requireActivity(),
       request
   ) as CreatePublicKeyCredentialResponse
} catch (e: CreateCredentialException) {
   configureProgress(View.INVISIBLE)
   handlePasskeyFailure(e)
}
  • Anda akan meneruskan informasi yang diperlukan ke createCredential().
  • Setelah permintaan berhasil, sheet bawah akan muncul di layar yang meminta Anda membuat kunci sandi.
  • Pengguna kini dapat memverifikasi identitas mereka melalui biometrik, kunci layar, dll.
  • Anda akan menangani visibilitas tampilan yang dirender dan menangani pengecualian jika permintaan gagal atau tidak berhasil karena beberapa alasan. Di sini, pesan error akan dicatat dalam log dan ditampilkan di aplikasi dalam dialog error. Anda dapat memeriksa log error lengkap melalui Android Studio atau perintah debug adb

93022cb87c00f1fc.png

  1. Terakhir, Anda perlu menyelesaikan proses pendaftaran dengan mengirimkan kredensial kunci publik ke server dan mengizinkan pengguna masuk. Aplikasi akan menerima objek kredensial yang berisi kunci publik yang dapat Anda kirim ke server untuk mendaftarkan kunci sandi.

Di sini, kita telah menggunakan server tiruan, jadi kita hanya menampilkan nilai benar yang menunjukkan bahwa server telah menyimpan kunci publik terdaftar untuk tujuan autentikasi dan validasi mendatang.

Di dalam metode signUpWithPasskeys(), temukan komentar yang relevan dan ganti dengan kode berikut:

SignUpFragment.kt

//TODO : complete the registration process after sending public key credential to your server and let the user in

data?.let {
   registerResponse()
   DataProvider.setSignedInThroughPasskeys(true)
   listener.showHome()
}
  • registerResponse menampilkan nilai benar yang menunjukkan server (tiruan) telah menyimpan kunci publik untuk digunakan pada masa mendatang.
  • Anda menetapkan flag setSignedInThroughPasskeys sebagai benar, yang menunjukkan bahwa Anda login melalui kunci sandi.
  • Setelah login, Anda mengalihkan pengguna ke layar utama.

Cuplikan kode berikut berisi opsi contoh yang akan Anda terima:

{
  "id": String,
  "rawId": String,
  "type": "public-key",
  "response": {
    "clientDataJSON": String,
    "attestationObject": String,
  }
}

Tabel berikut tidak lengkap, tetapi berisi parameter penting dalam PublicKeyCredential:

Parameter

Deskripsi

id

ID yang dienkode Base64URL dari kunci sandi yang dibuat. ID ini membantu browser menentukan apakah kunci sandi yang cocok ada di perangkat saat autentikasi. Nilai ini harus disimpan pada database di backend.

rawId

Versi objek ArrayBuffer dari ID kredensial.

response.clientDataJSON

Data klien yang dienkode objek ArrayBuffer.

response.attestationObject

Objek pengesahan yang dienkode ArrayBuffer. Objek ini berisi informasi penting, seperti ID RP, flag, dan kunci publik.

Jalankan aplikasi, Anda akan dapat mengklik tombol Daftar dengan kunci sandi dan membuat kunci sandi.

4. Menyimpan sandi di Penyedia Kredensial

Di aplikasi ini, di dalam layar Pendaftaran Anda, Anda sudah mendaftar dengan nama pengguna dan sandi yang diterapkan untuk tujuan demonstrasi.

Untuk menyimpan kredensial sandi pengguna dengan penyedia sandi mereka, Anda akan menerapkan CreatePasswordRequest untuk diteruskan ke createCredential() guna menyimpan sandi.

  • Temukan metode signUpWithPassword(), ganti TODO dengan panggilan createPassword:

SignUpFragment.kt

//TODO : Save the user credential password with their password provider

createPassword()
  • Di dalam metode createPassword(), Anda perlu membuat permintaan sandi seperti ini, ganti TODO dengan kode berikut:

SignUpFragment.kt

//TODO : CreatePasswordRequest with entered username and password

val request = CreatePasswordRequest(
   binding.username.text.toString(),
   binding.password.text.toString()
)
  • Selanjutnya, di dalam metode createPassword(), Anda perlu membuat kredensial dengan membuat permintaan sandi dan menyimpan kredensial sandi pengguna dengan penyedia sandinya, ganti TODO dengan kode berikut:

SignUpFragment.kt

//TODO : Create credential with created password request


try {
   credentialManager.createCredential(request, requireActivity()) as CreatePasswordResponse
} catch (e: Exception) {
   Log.e("Auth", " Exception Message : " + e.message)
}
  • Sekarang Anda telah berhasil menyimpan kredensial sandi dengan penyedia sandi pengguna untuk melakukan autentikasi melalui sandi hanya dalam sekali ketuk.

5. Menambahkan kemampuan untuk melakukan autentikasi dengan kunci sandi atau sandi

Kini Anda siap menggunakannya sebagai cara untuk mengautentikasi aplikasi Anda dengan aman.

629001f4a778d4fb.png

Dapatkan verifikasi login dan opsi lainnya untuk meneruskan panggilan getPasskey()

Sebelum meminta pengguna melakukan autentikasi, Anda harus meminta parameter untuk meneruskan json WebAuthn dari server, termasuk verifikasi login.

Anda sudah memiliki respons tiruan di aset Anda (AuthFromServer.txt) yang menampilkan parameter tersebut dalam codelab ini.

  • Di aplikasi Anda, buka SignInFragment.kt, temukan metode signInWithSavedCredentials tempat Anda akan menulis logika untuk melakukan autentikasi melalui kunci sandi atau sandi tersimpan dan membiarkan pengguna masuk:
  • Periksa blok else dengan komentar untuk memanggil createPasskey(), lalu ganti dengan kode berikut:

SignInFragment.kt

//TODO : Call getSavedCredentials() method to signin using passkey/password

val data = getSavedCredentials()
  • Di dalam metode getSavedCredentials(), Anda perlu membuat GetPublicKeyCredentialOption() dengan parameter yang diperlukan untuk mendapatkan kredensial dari penyedia kredensial Anda.

SigninFragment.kt

//TODO create a GetPublicKeyCredentialOption() with necessary registration json from server

val getPublicKeyCredentialOption =
   GetPublicKeyCredentialOption(fetchAuthJsonFromServer(), null)

fetchAuthJsonFromServer() ini merupakan sebuah metode, yang membaca respons json autentikasi dari aset dan menampilkan json autentikasi untuk mengambil semua kunci sandi yang terkait dengan akun pengguna ini.

Parameter ke-2: clientDataHash - hash yang digunakan untuk memverifikasi identitas pihak tepercaya, hanya akan ditetapkan jika Anda telah menetapkan GetCredentialRequest.origin. Untuk aplikasi contoh, parameter ini bernilai nol.

Parameter ke-3 akan bernilai benar jika Anda menginginkan operasi untuk ditampilkan segera ketika tidak ada kredensial yang tersedia daripada kembali untuk menemukan kredensial jarak jauh, dan akan bernilai salah (default) jika sebaliknya.

  • Temukan metode fetchAuthJsonFromServer() dan ganti TODO dengan kode berikut untuk menampilkan json serta menghapus pernyataan return string yang kosong:

SignInFragment.kt

//TODO fetch authentication mock json

return requireContext().readFromAsset("AuthFromServer")

Catatan: Server codelab ini didesain untuk menampilkan JSON yang sangat mirip dengan kamus PublicKeyCredentialRequestOptions yang diteruskan ke panggilan getCredential() API. Cuplikan kode berikut mencakup beberapa opsi contoh yang akan Anda terima:

{
  "challenge": String,
  "rpId": String,
  "userVerification": "",
  "timeout": 1800000
}

Tabel berikut tidak lengkap, tetapi berisi parameter penting dalam kamus PublicKeyCredentialRequestOptions:

Parameter

Deskripsi

challenge

Verifikasi login yang dibuat server di objek ArrayBuffer. Hal ini diperlukan untuk mencegah serangan replay. Jangan terima verifikasi login yang sama dalam satu respons. Pertimbangkan token CSRF.

rpId

ID RP adalah domain. Situs dapat menentukan domain atau akhiran yang dapat didaftarkan. Nilai ini harus cocok dengan parameter rp.id yang digunakan saat kunci sandi dibuat.

  • Berikutnya, Anda perlu membuat objek PasswordOption() untuk mengambil semua sandi tersimpan yang disimpan di penyedia sandi Anda melalui Credential Manager API untuk akun pengguna ini. Di dalam metode getSavedCredentials(), temukan TODO dan ganti dengan berikut ini:

SigninFragment.kt

//TODO create a PasswordOption to retrieve all the associated user's password

val getPasswordOption = GetPasswordOption()

Dapatkan kredensial

  • Selanjutnya Anda perlu memanggil permintaan getCredential() dengan semua opsi di atas untuk mengambil kredensial terkait:

SignInFragment.kt

//TODO call getCredential() with required credential options

val result = try {
   credentialManager.getCredential(
       requireActivity(),
       GetCredentialRequest(
           listOf(
               getPublicKeyCredentialOption,
               getPasswordOption
           )  
     )
   )
} catch (e: Exception) {
   configureViews(View.INVISIBLE, true)
   Log.e("Auth", "getCredential failed with exception: " + e.message.toString())
   activity?.showErrorAlert(
       "An error occurred while authenticating through saved credentials. Check logs for additional details"
   )
   return null
}

if (result.credential is PublicKeyCredential) {
   val cred = result.credential as PublicKeyCredential
   DataProvider.setSignedInThroughPasskeys(true)
   return "Passkey: ${cred.authenticationResponseJson}"
}
if (result.credential is PasswordCredential) {
   val cred = result.credential as PasswordCredential
   DataProvider.setSignedInThroughPasskeys(false)
   return "Got Password - User:${cred.id} Password: ${cred.password}"
}
if (result.credential is CustomCredential) {
   //If you are also using any external sign-in libraries, parse them here with the utility functions provided.
}

  • Anda akan meneruskan informasi yang diperlukan ke getCredential(). Tindakan ini memerlukan daftar opsi kredensial dan konteks aktivitas untuk merender opsi di sheet bawah dalam konteks tersebut.
  • Setelah permintaan berhasil, sheet bawah akan muncul di layar Anda dan mencantumkan semua kredensial yang dibuat untuk akun terkait.
  • Pengguna kini dapat memverifikasi identitas mereka melalui biometrik, kunci layar, dll. untuk mengautentikasi kredensial pilihan.
  • Anda menetapkan flag setSignedInThroughPasskeys sebagai benar, yang menunjukkan bahwa Anda login melalui kunci sandi. Jika tidak, flag akan bernilai salah.
  • Anda akan menangani visibilitas tampilan yang dirender dan menangani pengecualian jika permintaan gagal atau tidak berhasil karena beberapa alasan. Di sini, pesan error akan dicatat dalam log dan ditampilkan di aplikasi dalam dialog error. Anda dapat memeriksa log error lengkap melalui Android Studio atau perintah debug adb
  • Terakhir, Anda perlu menyelesaikan proses pendaftaran dengan mengirimkan kredensial kunci publik ke server dan mengizinkan pengguna masuk. Aplikasi menerima objek kredensial yang berisi kunci publik yang dapat Anda kirim ke server untuk melakukan autentikasi melalui kunci sandi.

Di sini, kita telah menggunakan server tiruan, jadi kita hanya menampilkan nilai benar yang menunjukkan bahwa server telah memvalidasi kunci publik.

Di dalam metode signInWithSavedCredentials, temukan komentar yang relevan dan ganti dengan kode berikut:

SignInFragment.kt

//TODO : complete the authentication process after validating the public key credential to your server and let the user in.

data?.let {
   sendSignInResponseToServer()
   listener.showHome()
}
  • sendSigninResponseToServer() menampilkan nilai benar yang menunjukkan server (tiruan) telah memvalidasi kunci publik untuk penggunaan pada masa mendatang.
  • Setelah login, Anda mengalihkan pengguna ke layar utama.

Cuplikan kode berikut menyertakan contoh objek PublicKeyCredential:

{
  "id": String
  "rawId": String
  "type": "public-key",
  "response": {
    "clientDataJSON": String
    "authenticatorData": String
    "signature": String
    "userHandle": String
  }
}

Tabel berikut tidaklah lengkap, tetapi berisi parameter penting dalam objek PublicKeyCredential:

Parameter

Deskripsi

id

ID yang dienkode Base64URL dari kredensial kunci sandi yang diautentikasi.

rawId

Versi objek ArrayBuffer dari ID kredensial.

response.clientDataJSON

Objek ArrayBuffer data klien. Kolom ini berisi informasi, seperti verifikasi login dan asal yang perlu diverifikasi oleh server RP.

response.authenticatorData

Objek ArrayBuffer data pengautentikasi. Kolom ini berisi informasi seperti ID RP.

response.signature

Objek ArrayBuffer tanda tangan. Nilai ini adalah inti kredensial dan harus diverifikasi di server.

response.userHandle

Objek ArrayBuffer yang berisi ID pengguna yang ditetapkan pada waktu pembuatan. Nilai ini dapat digunakan sebagai ganti ID kredensial jika server perlu memilih nilai ID yang digunakannya, atau jika backend ingin menghindari pembuatan indeks pada ID kredensial.

Jalankan aplikasi, buka halaman login -> Login dengan kunci sandi/sandi tersimpan, lalu coba login menggunakan kredensial tersimpan.

Cobalah

Anda telah menerapkan pembuatan kunci sandi, penyimpanan sandi di Credential Manager, dan autentikasi melalui kunci sandi atau sandi tersimpan menggunakan Credential Manager API di aplikasi Android Anda.

6. Selamat!

Anda telah menyelesaikan codelab ini. Jika ingin memeriksa resolusi akhir, Anda dapat membukanya di https://github.com/android/identity-samples/tree/main/CredentialManager

Jika ada pertanyaan, ajukan di StackOverflow dengan tag passkey.

Pelajari lebih lanjut