1. Prima di iniziare
Le soluzioni di autenticazione tradizionali presentano una serie di problemi di sicurezza e usabilità.
Le password sono ampiamente utilizzate, ma…
- Facilmente dimenticati
- Gli utenti devono avere le conoscenze necessarie per creare password efficaci.
- Facile da rubare, raccogliere e riprodurre dagli aggressori.
Android ha lavorato alla creazione dell'API Credential Manager per semplificare l'esperienza di accesso e affrontare i rischi per la sicurezza supportando le passkey, lo standard di settore di nuova generazione per l'autenticazione senza password.
Gestore delle credenziali riunisce il supporto delle passkey e lo combina con i metodi di autenticazione tradizionali come password, Accedi con Google e così via.
Gli utenti potranno creare passkey, memorizzarle in Gestore delle password di Google, che le sincronizzerà sui dispositivi Android su cui hanno eseguito l'accesso. Prima che un utente possa accedere con una passkey, questa deve essere creata, associata a un account utente e avere la chiave pubblica memorizzata su un server.
In questo codelab, imparerai come registrarti utilizzando passkey e password tramite l'API Credential Manager e come utilizzarle per scopi di autenticazione futuri. Esistono due flussi, tra cui:
- Registrazione : utilizzo di passkey e password.
- Accedi : utilizza passkey e password salvata.
Prerequisiti
- Conoscenza di base di come eseguire app in Android Studio.
- Conoscenza di base del flusso di autenticazione nelle app per Android.
- Conoscenza di base delle passkey.
Obiettivi didattici
- Come creare una passkey.
- Come salvare la password nel Gestore delle password.
- Come autenticare gli utenti con una passkey o una password salvata.
Che cosa ti serve
Una delle seguenti combinazioni di dispositivi:
- Un dispositivo Android con Android 9 o versioni successive (per le passkey) e Android 4.4 o versioni successive(per l'autenticazione tramite password tramite l'API Credential Manager).
- Dispositivo preferibilmente con un sensore biometrico.
- Assicurati di registrare un dato biometrico (o un blocco schermo).
- Versione del plug-in Kotlin : 1.8.10
2. Configurazione
- Clona questo repository sul tuo laptop dal ramo credman_codelab : https://github.com/android/identity-samples/tree/credman_codelab
git clone -b credman_codelab https://github.com/android/identity-samples.git
- Vai al modulo CredentialManager e apri il progetto in Android Studio.
Vediamo lo stato iniziale dell'app
Per scoprire come funziona lo stato iniziale dell'app:
- Avvia l'app.
- Viene visualizzata una schermata principale con un pulsante di registrazione e accesso. Questi pulsanti non fanno ancora nulla, ma attiveremo la loro funzionalità nelle sezioni successive.
3. Aggiungere la possibilità di registrarsi utilizzando le passkey
Quando si registrano per un nuovo account su un'app per Android che utilizza l'API Credential Manager, gli utenti possono creare una passkey per il proprio account. Questa passkey verrà archiviata in modo sicuro nel provider di credenziali scelto dall'utente e utilizzata per gli accessi futuri, senza che l'utente debba inserire la password ogni volta.
Ora dovrai creare una passkey e registrare le credenziali utente utilizzando la biometria/il blocco schermo.
Registrati con la passkey
Il codice all'interno di Credential Manager/app/main/java/SignUpFragment.kt definisce un campo di testo "username" e un pulsante per registrarsi con una passkey.
Passa la sfida e l'altra risposta JSON a una chiamata createPasskey()
Prima di creare una passkey, devi richiedere al server le informazioni necessarie da passare all'API Credential Manager durante la chiamata createCredential().
Nei tuoi asset di progetto è già presente una risposta simulata, denominata RegFromServer.txt, che restituisce i parametri necessari in questo codelab.
- Nella tua app, vai a SignUpFragment.kt, trova il metodo signUpWithPasskeys in cui scriverai la logica per creare una passkey e consentire all'utente di accedere. Puoi trovare il metodo nella stessa classe.
- Controlla il blocco else con un commento per chiamare
createPasskey()
e sostituiscilo con il seguente codice:
SignUpFragment.kt
//TODO : Call createPasskey() to signup with passkey
val data = createPasskey()
Questo metodo verrà chiamato dopo che avrai inserito un nome utente valido sullo schermo.
- All'interno del metodo
createPasskey()
, devi creare unCreatePublicKeyCredentialRequest()
con i parametri necessari restituiti.
SignUpFragment.kt
//TODO create a CreatePublicKeyCredentialRequest() with necessary registration json from server
val request = CreatePublicKeyCredentialRequest(fetchRegistrationJsonFromServer())
Il metodo fetchRegistrationJsonFromServer()
legge una risposta JSON PublicKeyCredentialCreationOptions
del server emulato dalle risorse e restituisce il JSON di registrazione da passare durante la creazione della passkey.
- Trova il metodo
fetchRegistrationJsonFromServer()
e sostituisci il TODO con il seguente codice per restituire JSON e rimuovere l'istruzione di ritorno della stringa vuota :
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())
- Questo JSON è incompleto e contiene 4 campi che devono essere sostituiti.
- UserId deve essere univoco in modo che un utente possa creare più passkey (se necessario). Sostituisci
<userId>
con il valoreuserId
generato. - Anche
<challenge>
deve essere univoco, quindi genererai una sfida univoca casuale. Il metodo è già presente nel codice.
La risposta PublicKeyCredentialCreationOptions
di un server reale potrebbe restituire più opzioni. Di seguito è riportato un esempio di alcuni di questi campi:
{
"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"
}
}
La tabella seguente illustra alcuni dei parametri importanti di un oggetto PublicKeyCredentialCreationOptions
:
Parametri | Descrizioni |
Una stringa casuale generata dal server che contiene entropia sufficiente a renderne impossibile la deduzione. Deve avere una lunghezza di almeno 16 byte. Questo valore è obbligatorio, ma non viene utilizzato durante la registrazione, a meno che non venga eseguita l'attestazione. | |
L'ID univoco di un utente. Questo valore non deve includere informazioni che consentono l'identificazione personale, ad esempio indirizzi email o nomi utente. Un valore casuale di 16 byte generato per account andrà bene. | |
Questo campo deve contenere un identificatore univoco per l'account che l'utente riconoscerà, ad esempio il suo indirizzo email o il suo nome utente. Verrà visualizzato nel selettore account. Se utilizzi un nome utente, utilizza lo stesso valore dell'autenticazione con password. | |
Questo campo è un nome facoltativo e più intuitivo per l'account. | |
La persona giuridica della terza parte attendibile corrisponde ai dettagli della tua applicazione. Ha i seguenti attributi:
| |
Elenco di algoritmi e tipi di chiavi consentiti. Questo elenco deve contenere almeno un elemento. | |
L'utente che sta tentando di registrare un dispositivo potrebbe aver registrato altri dispositivi. Per limitare la creazione di più credenziali per lo stesso account su un singolo authenticator, puoi ignorare questi dispositivi. L'elemento | |
Indica se il dispositivo deve essere collegato alla piattaforma o meno o se non è necessario. Imposta questo valore su | |
| Indica il valore |
Crea una credenziale
- Dopo aver creato un
CreatePublicKeyCredentialRequest()
, devi chiamare la chiamatacreateCredential()
con la richiesta creata.
SignUpFragment.kt
//TODO call createCredential() with createPublicKeyCredentialRequest
try {
response = credentialManager.createCredential(
requireActivity(),
request
) as CreatePublicKeyCredentialResponse
} catch (e: CreateCredentialException) {
configureProgress(View.INVISIBLE)
handlePasskeyFailure(e)
}
- Trasmetti le informazioni richieste a
createCredential()
. - Una volta che la richiesta è andata a buon fine, sullo schermo viene visualizzato un riquadro in basso che ti chiede di creare una passkey.
- Ora gli utenti possono verificare la propria identità tramite dati biometrici o blocco schermo e così via.
- Gestisci la visibilità delle visualizzazioni visualizzate e le eccezioni se la richiesta non va a buon fine o non va a buon fine per qualche motivo. Qui i messaggi di errore vengono registrati e visualizzati nell'app in una finestra di dialogo di errore. Puoi controllare i log degli errori completi tramite Android Studio o il comando
adb debug
.
- Infine, devi completare la procedura di registrazione. L'app invia una credenziale della chiave pubblica al server, che la registra per l'utente corrente.
Qui abbiamo utilizzato un server simulato, quindi restituiamo semplicemente true per indicare che il server ha salvato la chiave pubblica registrata a fini di autenticazione e convalida futuri. Per scoprire di più sulla registrazione delle passkey lato server per la tua implementazione, consulta la pagina dedicata.
All'interno del metodo signUpWithPasskeys()
, trova il commento pertinente e sostituiscilo con il seguente codice:
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()
restituiscetrue
per indicare che il server simulato ha salvato la chiave pubblica per un uso futuro.- Imposta il flag
setSignedInThroughPasskeys
sutrue
. - Una volta eseguito l'accesso, reindirizza l'utente alla schermata Home.
Un PublicKeyCredential
reale può contenere più campi. Di seguito è riportato un esempio di questi campi:
{
"id": String,
"rawId": String,
"type": "public-key",
"response": {
"clientDataJSON": String,
"attestationObject": String,
}
}
La tabella seguente illustra alcuni dei parametri importanti di un oggetto PublicKeyCredential
:
Parametri | Descrizioni |
Un ID con codifica Base64URL della passkey creata. Questo ID aiuta il browser a determinare se una passkey corrispondente è presente nel dispositivo al momento dell'autenticazione. Questo valore deve essere memorizzato nel database sul backend. | |
Una versione dell'oggetto | |
Un oggetto | |
Un oggetto attestazione codificato in |
Esegui l'app e potrai fare clic sul pulsante Registrati con le passkey e creare una passkey.
4. Salvare una password nel Provider delle credenziali
In questa app, nella schermata di registrazione, è già stata implementata una registrazione con nome utente e password a scopo dimostrativo.
Per salvare la credenziale della password dell'utente con il relativo provider, dovrai implementare un CreatePasswordRequest
da passare a createCredential()
per salvare la password.
- Trova il metodo
signUpWithPassword()
, sostituisci il TODO con una chiamata acreatePassword
:
SignUpFragment.kt
//TODO : Save the user credential password with their password provider
createPassword()
- All'interno del metodo
createPassword()
, devi creare una richiesta di password come questa, sostituendo il TODO con il seguente codice:
SignUpFragment.kt
//TODO : CreatePasswordRequest with entered username and password
val request = CreatePasswordRequest(
binding.username.text.toString(),
binding.password.text.toString()
)
- Successivamente, all'interno del metodo
createPassword()
, crea una credenziale con una richiesta di creazione della password e salva la credenziale della password dell'utente con il relativo provider. Sostituisci il TODO con il seguente codice:
SignUpFragment.kt
//TODO : Create credential with created password request
try {
credentialManager.createCredential(requireActivity(), request) as CreatePasswordResponse
} catch (e: Exception) {
Log.e("Auth", " Exception Message : " + e.message)
}
- Ora hai salvato correttamente la credenziale della password con il provider della password dell'utente per autenticarti con una password con un solo tocco.
5. Aggiungere la possibilità di autenticarsi con una passkey o una password
Ora puoi utilizzarla per autenticarti in sicurezza nella tua app.
Ottieni la sfida e altre opzioni da passare alla chiamata getPasskey()
Prima di chiedere all'utente di autenticarsi, devi richiedere i parametri da passare in JSON WebAuthn dal server, inclusa una verifica.
Nei tuoi asset (AuthFromServer.txt) è già presente una risposta simulata che restituisce questi parametri in questo codelab.
- Nella tua app, vai a SignInFragment.kt, trova il metodo
signInWithSavedCredentials
in cui scriverai la logica per l'autenticazione tramite passkey o password salvate e fai accedere l'utente: - Controlla il blocco else con un commento per chiamare
createPasskey()
e sostituiscilo con il seguente codice:
SignInFragment.kt
//TODO : Call getSavedCredentials() method to signin using passkey/password
val data = getSavedCredentials()
- All'interno del metodo getSavedCredentials(), devi creare un
GetPublicKeyCredentialOption()
con i parametri necessari per ottenere le credenziali dal tuo fornitore di credenziali.
SigninFragment.kt
//TODO create a GetPublicKeyCredentialOption() with necessary registration json from server
val getPublicKeyCredentialOption =
GetPublicKeyCredentialOption(fetchAuthJsonFromServer(), null)
Il metodo fetchAuthJsonFromServer()
legge la risposta JSON di autenticazione dagli asset e restituisce il JSON di autenticazione per recuperare tutte le passkey associate a questo account utente.
Il secondo parametro di GetPublicKeyCredentialOption() è clientDataHash
, un hash utilizzato per verificare l'identità della terza parte attendibile. Imposta questo valore solo se hai impostato GetCredentialRequest.origin
. Per l'app di esempio, questo valore è impostato su null
.
- Individua il metodo fetchAuthJsonFromServer() e sostituisci il TODO con il seguente codice per restituire il JSON e rimuovi l'istruzione di ritorno della stringa vuota:
SignInFragment.kt
//TODO fetch authentication mock json
return requireContext().readFromAsset("AuthFromServer")
Nota : il server di questo codelab è progettato per restituire un JSON il più simile possibile al dizionario PublicKeyCredentialRequestOptions
passato alla chiamata getCredential() dell'API. Il seguente snippet di codice include alcune opzioni di esempio che potresti ricevere in una risposta reale:
{
"challenge": String,
"rpId": String,
"userVerification": "",
"timeout": 1800000
}
La tabella seguente illustra alcuni dei parametri importanti di un oggetto PublicKeyCredentialRequestOptions
:
Parametri | Descrizioni |
Una verifica generata dal server in un oggetto | |
Un ID RP è un dominio. Un sito web può specificare il proprio dominio o un sufisso registrabile. Questo valore deve corrispondere al parametro |
- Successivamente, devi creare un oggetto
PasswordOption()
per recuperare tutte le password salvate nel tuo provider di password tramite l'API Credential Manager per questo account utente. All'interno del metodogetSavedCredentials()
, trova il TODO e sostituiscilo con quanto segue:
SigninFragment.kt
//TODO create a PasswordOption to retrieve all the associated user's password
val getPasswordOption = GetPasswordOption()
Recupera le credenziali
- Successivamente, devi chiamare la richiesta
getCredential()
con tutte le opzioni sopra indicate per recuperare le credenziali associate:
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.
}
- Trasmetti le informazioni richieste a
getCredential()
. Viene utilizzato l'elenco delle opzioni di credenziali e un contesto dell'attività per visualizzare le opzioni nel riquadro in basso in quel contesto. - Una volta che la richiesta è andata a buon fine, sullo schermo viene visualizzato un riquadro in basso con tutte le credenziali create per l'account associato.
- Ora gli utenti possono verificare la propria identità tramite dati biometrici o blocco schermo e così via per autenticare la credenziale scelta.
- Se la credenziale scelta è
PublicKeyCredential
, imposta il flagsetSignedInThroughPasskeys
sutrue
. In caso contrario, impostalo sufalse
.
Il seguente snippet di codice include un oggetto PublicKeyCredential
di esempio:
{
"id": String
"rawId": String
"type": "public-key",
"response": {
"clientDataJSON": String
"authenticatorData": String
"signature": String
"userHandle": String
}
}
La tabella seguente non è esaustiva, ma contiene i parametri importanti dell'oggetto PublicKeyCredential
:
Parametri | Descrizioni |
L'ID con codifica Base64URL della credenziale della passkey autenticata. | |
Una versione dell'oggetto | |
Un oggetto | |
Un oggetto | |
Un oggetto | |
Un oggetto |
- Infine, devi completare la procedura di autenticazione. Normalmente, dopo che l'utente ha completato l'autenticazione con passkey, l'app invia al server una credenziale con chiave pubblica contenente un'affermazione di autenticazione che verifica l'affermazione e autentica l'utente.
Qui abbiamo utilizzato un server simulato, quindi restituiamo semplicemente true
per indicare che il server ha verificato l'affermazione. Per scoprire di più sull'autenticazione delle passkey lato server per la tua implementazione, consulta la pagina dedicata.
All'interno del metodo signInWithSavedCredentials()
, trova il commento pertinente e sostituiscilo con il seguente codice:
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()
restituisce true per indicare che il server (simulato) ha convalidato la chiave pubblica per l'utilizzo futuro.- Una volta eseguito l'accesso, reindirizza l'utente alla schermata Home.
Esegui l'app e vai ad Accedi > Accedi con passkey/password salvata e prova ad accedere utilizzando le credenziali salvate.
Prova
Hai implementato la creazione di passkey, il salvataggio della password in Gestore delle credenziali e l'autenticazione tramite passkey o password salvata utilizzando l'API Gestore delle credenziali nella tua app per Android.
6. Complimenti!
Hai completato questo codelab. Se vuoi controllare la soluzione finale, che è disponibile all'indirizzo https://github.com/android/identity-samples/tree/main/CredentialManager
In caso di domande, chiedile su Stack Overflow con un tag passkey
.