Pengelola Kredensial adalah API Jetpack yang mendukung beberapa metode login, seperti nama pengguna dan sandi, kunci sandi, serta solusi login gabungan (seperti Login dengan Google) dalam satu API, sehingga menyederhanakan integrasi bagi developer.
Selain itu, untuk pengguna, Pengelola Kredensial menyatukan antarmuka login di seluruh metode autentikasi, sehingga lebih jelas dan lebih mudah bagi pengguna untuk login ke aplikasi, terlepas dari metode yang mereka pilih.
Tentang kunci sandi
Kunci sandi adalah pengganti sandi yang lebih aman dan lebih mudah. Dengan kunci sandi, pengguna dapat login ke aplikasi dan situs menggunakan sensor biometrik (seperti sidik jari atau pengenalan wajah), PIN, atau pola. Hal ini memberikan pengalaman login yang lancar, sehingga pengguna tidak perlu mengingat nama pengguna atau sandi.
Kunci sandi mengandalkan WebAuthn (Web Authentication), sebuah standar yang dikembangkan bersama oleh FIDO Alliance dan World Wide Web Consortium (W3C). WebAuthn menggunakan kriptografi kunci publik untuk mengautentikasi pengguna. Situs atau aplikasi tempat pengguna login dapat melihat dan menyimpan kunci publik, tetapi tidak pernah bisa mengakses kunci pribadi. Kunci pribadi dirahasiakan dan aman. Dan karena kunci ini unik dan terikat dengan situs atau aplikasi, kunci sandi tidak dapat disalahgunakan sehingga menambah keamanan lebih lanjut.
Dengan Pengelola Kredensial, pengguna dapat membuat kunci sandi dan menyimpannya di Pengelola Sandi Google.
Prasyarat
Untuk menggunakan Pengelola Kredensial, selesaikan langkah-langkah di bagian ini.
Menggunakan versi platform terbaru
Pengelola Kredensial didukung di Android 4.4 (API level 19) dan yang lebih tinggi.
Menambahkan dependensi ke aplikasi
Tambahkan dependensi berikut ke file build.gradle
modul aplikasi Anda:
Groovy
dependencies { implementation "androidx.credentials:credentials:1.0.0-alpha02" // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation "androidx.credentials:credentials-play-services-auth:1.0.0-alpha02" }
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.0.0-alpha02") // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation("androidx.credentials:credentials-play-services-auth:1.0.0-alpha02") }
Menambahkan dukungan untuk Digital Asset Links
Guna mengaktifkan dukungan untuk kunci sandi aplikasi Android, kaitkan aplikasi Anda dengan situs yang dimiliki aplikasi Anda. Anda dapat mendeklarasikan pengaitan ini dengan menyelesaikan langkah-langkah berikut:
Membuat file JSON Digital Asset Links. Misalnya, untuk mendeklarasikan bahwa situs
https://signin.example.com
dan aplikasi Android dengan nama paketcom.example
dapat berbagi kredensial login, buat file bernamaassetlinks.json
dengan konten berikut:[{ "relation": ["delegate_permission/common.get_login_creds"], "target": { "namespace": "web", "site": "https://signin.example.com" } }, { "relation": ["delegate_permission/common.get_login_creds"], "target": { "namespace": "android_app", "package_name": "com.example", "sha256_cert_fingerprints": [ SHA_HEX_VALUE ] } }]
Kolom
relation
adalah array dari satu atau beberapa string yang menjelaskan hubungan yang dideklarasikan. Untuk mendeklarasikan bahwa aplikasi dan situs berbagi kredensial login, tentukan stringdelegate_permission/common.get_login_creds
.Kolom
target
adalah objek yang menentukan aset tempat deklarasi berlaku. Kolom berikut mengidentifikasi situs:namespace
web
site
URL situs, dalam format
https://domain[:optional_port]
; misalnya,https://www.example.com
.domain harus sepenuhnya memenuhi syarat, dan optional_port harus dihilangkan saat menggunakan port 443 untuk HTTPS.
Target
site
hanya dapat berupa domain root: Anda tidak dapat membatasi pengaitan aplikasi ke subdirektori tertentu. Jangan sertakan jalur dalam URL, seperti garis miring.Subdomain tidak dianggap cocok: yaitu, jika Anda menentukan domain sebagai
www.example.com
, domainwww.counter.example.com
tidak akan dikaitkan dengan aplikasi Anda.Kolom berikut mengidentifikasi aplikasi Android:
namespace
android_app
package_name
Nama paket yang dideklarasikan dalam manifes aplikasi. Misalnya, com.example.android
sha256_cert_fingerprints
Sidik jari SHA256 dari sertifikat penandatanganan aplikasi Anda. Menghosting file JSON Digital Assets Link di lokasi berikut pada domain login:
https://domain[:optional_port]/.well-known/assetlinks.json
Misalnya, jika domain login Anda adalah
signin.example.com
, hosting file JSON dihttps://signin.example.com/.well-known/assetlinks.json
.Jenis MIME untuk file Digital Assets Link harus berupa JSON. Pastikan server mengirimkan header
Content-Type: application/json
dalam respons.Pastikan host Anda mengizinkan Google untuk mengambil file Digital Asset Link. Jika Anda memiliki file
robots.txt
, file tersebut harus mengizinkan agen Googlebot untuk mengambil/.well-known/assetlinks.json
. Sebagian besar situs mengizinkan agen otomatis untuk mengambil file di jalur/.well-known/
, sehingga layanan lain dapat mengakses metadata dalam file tersebut:User-agent: * Allow: /.well-known/
Mendeklarasikan pengaitan di aplikasi Android:
Tambahkan resource string
asset_statements
ke filestrings.xml
. Stringasset_statements
adalah objek JSON yang menentukan fileassetlinks.json
untuk dimuat. Anda harus meng-escape apostrof dan tanda kutip yang Anda gunakan dalam string. Contoh:<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
Mengonfigurasi Pengelola Kredensial
Untuk mengonfigurasi dan melakukan inisialisasi objek CredentialManager
, tambahkan logika yang mirip dengan
berikut ini:
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)
Memproses login pengguna
Untuk mengambil semua opsi kunci sandi dan sandi yang dikaitkan dengan akun pengguna, selesaikan langkah-langkah berikut:
Lakukan inisialisasi opsi autentikasi sandi dan kunci sandi:
Kotlin
// Retrieves the user's saved password for your app from their // password provider. val getPasswordOption = GetPasswordOption() // Get passkeys from the user's public key credential provider. val getPublicKeyCredentialOption = GetPublicKeyCredentialOption( requestJson = requestJson, preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials )
Java
// Retrieves the user's saved password for your app from their // password provider. GetPasswordOption getPasswordOption = new GetPasswordOption(); // Get passkeys from the user's public key credential provider. GetPublicKeyCredentialOption getPublicKeyCredentialOption = new GetPublicKeyCredentialOption(requestJson, preferImmediatelyAvailableCredentials);
Build permintaan login menggunakan opsi yang diambil dari langkah sebelumnya:
Kotlin
val getCredRequest = GetCredentialRequest( listOf(getPasswordOption, getPublicKeyCredentialOption) )
Java
GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder() .addCredentialOption(getPasswordOption) .addCredentialOption(getPublicKeyCredentialOption) .build();
Meluncurkan alur login:
Kotlin
coroutineScope.launch { try { val result = credentialManager.getCredential( request = getCredRequest, activity = activity, ) handleSignIn(result) } catch (e : GetCredentialException) { handleFailure(e) } } fun handleSignIn(result: GetCredentialResponse) { // Handle the successfully returned credential. val credential = result.credential when (credential) { is PublicKeyCredential -> { responseJson = credential.authenticationResponseJson fidoAuthenticateWithServer(responseJson) } is PasswordCredential -> { val username = credential.id val password = credential.password passwordAuthenticateWithServer(username, password) } else -> { // Catch any unrecognized credential type here. Log.e(TAG, "Unexpected type of credential") } } }
Java
credentialManager.getCredentialAsync( getCredRequest, activity, cancellationSignal, requireContext().getMainExecutor(), new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() { @Override public void onResult(GetCredentialResponse result) { // Handle the successfully returned credential. Credential credential = result.getCredential(); if (credential instanceof PublicKeyCredential) { String responseJson = ((PublicKeyCredential) credential) .getAuthenticationResponseJson(); fidoAuthenticateToServer(responseJson); } else if (credential instanceof PasswordCredential) { Log.d(TAG, "Got PasswordCredential"); String id = ((PasswordCredential) credential).getId(); String password = ((PasswordCredential) credential) .getPassword(); firebaseSignInWithPassword(id, password); } else { Log.e( TAG, "Unexpected type of credential: " + credential.getClass().getName()); } } @Override public void onError(GetCredentialException e) { Log.e(TAG, "Sign in failed with exception", e); } } );
Cuplikan berikut menunjukkan contoh cara memformat permintaan JSON saat Anda mendapatkan kunci sandi:
{
"challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
"allowCredentials": [],
"timeout": 1800000,
"userVerification": "required",
"rpId": "credential-manager-app-test.glitch.me"
}
Cuplikan kode berikut menunjukkan contoh respons JSON setelah Anda mendapatkan kredensial kunci publik:
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
"signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
"userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
}
}
Alur pendaftaran
Anda dapat mendaftarkan pengguna untuk autentikasi menggunakan kunci sandi atau sandi.
Membuat kunci sandi
Untuk memberi pengguna pilihan untuk mendaftarkan kunci sandi dan menggunakannya untuk autentikasi ulang,
daftarkan kredensial pengguna menggunakan objek 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( request = createPublicKeyCredentialRequest, activity = activity, ) 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 CreateCustomCredentialException -> { // 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( createPublicKeyCredentialRequest, requireActivity(), cancellationSignal, requireContext().getMainExecutor(), 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 CreateCustomCredentialException) { // 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()); } } } ); }
Cuplikan berikut menunjukkan contoh cara memformat permintaan JSON saat Anda membuat kunci sandi. Pelajari lebih lanjut cara membuat permintaan kunci sandi.
{
"challenge": "nhkQXfE59Jb97VyyNJkvDiXucMEvltduvcrDmGrODHY",
"rp": {
"name": "CredMan App Test",
"id": "credential-manager-app-test.glitch.me"
},
"user": {
"id": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0",
"name": "helloandroid@gmail.com",
"displayName": "helloandroid@gmail.com"
},
"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"
}
}
Cuplikan kode berikut menunjukkan contoh respons JSON untuk membuat kredensial kunci publik. Pelajari lebih lanjut cara menangani kredensial kunci publik yang ditampilkan.
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
}
}
Menyimpan sandi pengguna
Jika pengguna memberikan nama pengguna dan sandi untuk alur autentikasi di
aplikasi, Anda dapat mendaftarkan kredensial pengguna yang dapat digunakan untuk mengautentikasi
pengguna. Untuk melakukannya, buat objek CreatePasswordRequest
:
Kotlin
fun registerPassword(username: String, password: String) { // Initialize a CreatePasswordRequest object. val createPasswordRequest = CreatePasswordRequest(id = username, password = password) // Create credentials and handle result. coroutineScope.launch { try { val result = credentialManager.createCredential(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( createPasswordRequest, requireActivity(), cancellationSignal, requireContext().getMainExecutor(), new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> { @Override public void onResult(CreateCredentialResponse result) { handleResult(result); } @Override public void onError(CreateCredentialException e) { handleFailure(e) } } ); }
Memecahkan masalah error umum
Tabel berikut menampilkan beberapa kode dan deskripsi error umum, serta memberikan beberapa informasi terkait penyebabnya:
Kode dan deskripsi error | Penyebab |
---|---|
Kegagalan Login Awal: 16: Pemanggil diblokir untuk sementara karena terlalu banyak perintah login yang dibatalkan. | Jika mengalami periode tunggu 24 jam ini selama pengembangan, Anda dapat meresetnya dengan menghapus penyimpanan aplikasi layanan Google Play. Atau, untuk mengganti waktu tunggu ini pada emulator atau perangkat pengujian, buka
aplikasi Telepon dan masukkan kode berikut:
|
Kegagalan Login Awal: 8: Error internal tidak diketahui. |
|
CreatePublicKeyCredentialDomException: Permintaan masuk tidak dapat divalidasi | ID paket aplikasi tidak terdaftar di server Anda. Validasikan hal ini dalam integrasi sisi server Anda. |