Scopri come semplificare i percorsi di autenticazione utilizzando l'API Credential Manager nella tua app per Android

1. Prima di iniziare

Le soluzioni di autenticazione tradizionali pongono varie sfide in termini di sicurezza e usabilità.

Le password sono ampiamente utilizzate, ma...

  • Facilmente dimenticabile
  • Gli utenti richiedono conoscenze per creare password efficaci.
  • Facile da phishing, raccolta e riproduzione da parte degli aggressori.

Android ha lavorato alla creazione dell'API Gestore delle credenziali 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 per le passkey e lo combina con i metodi di autenticazione tradizionali come le password, Accedi con Google e così via.

Gli utenti potranno creare passkey e memorizzarle nel Gestore delle password di Google, che sincronizza le passkey sui dispositivi Android su cui l'utente ha eseguito l'accesso. Per consentire agli utenti di accedere alla passkey, è necessario creare una passkey, associarla a un account utente e archiviare la sua chiave pubblica su un server.

In questo codelab, imparerai a registrarti utilizzando passkey e password tramite l'API Credential Manager e a utilizzarle per l'autenticazione futuri. Esistono due flussi:

  • Registrati : utilizzando passkey e password.
  • Accedere : tramite passkey e password salvata.

Prerequisiti

  • Conoscenza di base dell'esecuzione di 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 in 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 della password tramite l'API Credential Manager).
  • Dispositivo preferibilmente dotato di sensore biometrico.
  • Assicurati di registrare un dato biometrico (o un blocco schermo).
  • Versione plug-in Kotlin : 1.8.10

2. Configurazione

  1. Clona questo repository sul tuo laptop dalla filiale credman_codelab : https://github.com/android/identity-samples/tree/credman_codelab
  2. Vai al modulo CredentialManager e apri il progetto in Android Studio.

Vediamo lo stato iniziale dell'app

Per controllare il funzionamento dello stato iniziale dell'app, segui questi passaggi:

  1. Avvia l'app.
  2. Viene visualizzata una schermata principale con un pulsante di registrazione e accesso.
  3. Puoi fare clic su Registrati per registrarti utilizzando una passkey o una password.
  4. Puoi fare clic su Accedi per usare passkey e password salvata.

8c0019ff9011950a.jpeg

Per capire cosa sono le passkey e come funzionano, vedi Come funzionano le passkey? .

3. Aggiungi la possibilità di registrarsi usando 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à memorizzata in modo sicuro sul fornitore di credenziali scelto dall'utente e utilizzata per gli accessi futuri, senza che l'utente debba inserire la password ogni volta.

Ora creerai una passkey e registrerai le credenziali utente usando la biometria/il blocco schermo.

Registrati con la passkey

Inside Credential Manager -> app -> principale -> java -> SignUpFragment.kt, vedrai il campo di testo "nome utente" e un pulsante per registrarti con la passkey.

dcc5c529b310f2fb.jpeg

Supera la sfida e altre risposte json per la chiamata createPasskey()

Prima di creare una passkey, devi richiedere al server di ottenere le informazioni necessarie da passare all'API Credential Manager durante la chiamata createCredential().

Fortunatamente, hai già una risposta fittizia nei tuoi asset(RegFromServer.txt) che restituisce questi parametri in questo codelab.

  • Nell'app, vai al metodo SignUpFragment.kt, Trova, signUpWithPasskeys, dove scriverai la logica per creare una passkey e consentire all'utente di accedere. Puoi trovare il metodo nella stessa classe.
  • Seleziona il blocco "other" con un commento per chiamare la funzione createPasskey() e sostituiscila con il seguente codice :

SignUpFragment.kt

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

val data = createPasskey()

Questo metodo verrà chiamato una volta che avrai compilato un nome utente valido sullo schermo.

  • All'interno del metodo createPasskey(), devi creare un'istruzione CreatePublicKeyCredentialRequest() con i parametri necessari restituiti.

SignUpFragment.kt

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

val request = CreatePublicKeyCredentialRequest(fetchRegistrationJsonFromServer())

Questo metodo fetchRegistrationJsonFromServer() legge la risposta json della registrazione dagli asset e restituisce il json della registrazione da passare durante la creazione della passkey.

  • Trova il metodo fetchRegistrationJsonFromServer() e sostituisci TODO con il seguente codice per restituire il json e rimuovi l'istruzione di restituzione 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())
  • Qui leggi il json della registrazione dalle risorse.
  • Questo json ha 4 campi da sostituire.
  • L'ID utente deve essere univoco per consentire a un utente di creare più passkey (se necessario). Tu sostituisci <userId> con lo userId generato.
  • &lt;challenge&gt; deve essere univoco, quindi verrà generato un test univoco casuale. Il metodo è già presente nel codice.

Il seguente snippet di codice include opzioni di esempio ricevute dal 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"
  }
}

La tabella seguente non è esaustiva, ma contiene i parametri importanti nel dizionario PublicKeyCredentialCreationOptions:

Parametri

Descrizioni

challenge

Una stringa casuale generata dal server che contiene abbastanza entropia da rendere inattuabile l'ipotesi. Deve avere una lunghezza di almeno 16 byte. Questi dati sono obbligatori, ma non vengono utilizzati durante la registrazione, salvo attestazione.

user.id

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.

user.name

Questo campo deve contenere un identificatore univoco dell'account che l'utente riconoscerà, come l'indirizzo email o il nome utente. Questo valore verrà visualizzato nel selettore degli account. Se usi un nome utente, usa lo stesso valore dell'autenticazione tramite password.

user.displayName

Questo campo è un nome facoltativo e più intuitivo per l'account. È un nome accessibile alle persone per l'account utente, destinato solo alla visualizzazione.

rp.id

L'entità parte integrante corrisponde ai dettagli della tua richiesta.Ha bisogno di :

  • un nome (obbligatorio): il nome dell'applicazione
  • un ID (facoltativo): corrisponde al dominio o al sottodominio. Se non è presente, viene utilizzato il dominio corrente.
  • un'icona (facoltativa).

pubKeyCredParams

Parametri credenziali chiave pubblica è un elenco di algoritmi e tipi di chiavi consentiti. Questo elenco deve contenere almeno un elemento.

excludeCredentials

L'utente che tenta di registrare un dispositivo potrebbe aver registrato altri dispositivi. Per limitare la creazione di più credenziali per lo stesso account su un singolo autenticatore, puoi ignorare questi dispositivi. Il membro trasporti, se fornito, deve contenere il risultato della chiamata a getTransports() durante la registrazione di ogni credenziale.

authenticatorSelection.authenticatorAttachment

indica se il dispositivo deve essere collegato alla piattaforma o meno o se non sussiste alcun requisito. Impostala su "platform". Questo indica che vogliamo un autenticatore incorporato nel dispositivo della piattaforma e all'utente non verrà chiesto di inserire, ad esempio, Un token di sicurezza USB.

residentKey

indica il valore "required" per creare una passkey.

Crea una credenziale

  1. Dopo aver creato un oggetto CreatePublicKeyCredentialRequest(), devi chiamare la chiamata createCredential() 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)
}
  • Devi passare le informazioni richieste a createCredential().
  • Una volta approvata la richiesta, sullo schermo verrà visualizzato un riquadro inferiore in cui ti viene chiesto di creare una passkey.
  • Ora gli utenti possono verificare la propria identità tramite la biometria, il blocco schermo e così via.
  • Puoi gestire la visibilità delle visualizzazioni visualizzate e le eccezioni se la richiesta non riesce o non va a buon fine per qualche motivo. Qui i messaggi di errore vengono registrati e mostrati nell'app in una finestra di dialogo di errore. Puoi controllare i log completi degli errori tramite il comando di debug di Android Studio o adb.

93022cb87c00f1fc.png

  1. Infine, devi completare il processo di registrazione inviando la credenziale della chiave pubblica al server e facendo accedere l'utente. L'app riceve un oggetto credenziali contenente una chiave pubblica che puoi inviare al server per registrare la passkey.

In questo caso abbiamo utilizzato un server fittizio, quindi restituiamo semplicemente "true" che indica che il server ha salvato la chiave pubblica registrata per scopi di autenticazione e convalida futuri.

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()
}
  • registryResponse restituisce true che indica che il server (simulativo) ha salvato la chiave pubblica per un uso futuro.
  • Hai impostato il flagSignedInTramitePasskeys su true per indicare che stai accedendo tramite passkey.
  • Una volta effettuato l'accesso, l'utente verrà reindirizzato alla schermata Home.

Il seguente snippet di codice contiene un esempio di opzioni che dovresti ricevere:

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

La tabella seguente non è esaustiva, ma contiene i parametri importanti in PublicKeyCredential:

Parametri

Descrizioni

id

Un ID codificato in Base64URL della passkey creata. Questo ID aiuta il browser a determinare se nel dispositivo è presente una passkey corrispondente al momento dell'autenticazione. Questo valore deve essere archiviato nel database del backend.

rawId

Una versione dell'oggetto ArrayBuffer dell'ID credenziale.

response.clientDataJSON

Un oggetto ArrayBuffer ha codificato i dati del client.

response.attestationObject

Un oggetto di attestazione codificato ArrayBuffer. Contiene informazioni importanti, ad esempio un ID parte soggetta a limitazioni, flag e una chiave pubblica.

Esegui l'app: potrai fare clic sul pulsante Registrati con le passkey e creare una passkey.

4. Salva la password in Provider di credenziali

In questa app, nella schermata di registrazione, è già presente una registrazione con nome utente e password implementati a scopo dimostrativo.

Per salvare la credenziale della password dell'utente con il relativo fornitore di password, devi implementare una richiesta CreatePasswordRequest che devi passare a createCredential() per salvare la password.

  • Trova il metodo signUpWithPassword(), sostituisci TODO con createPassword :

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, sostituire 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()
)
  • Quindi, all'interno del metodo createPassword(), devi creare la credenziale con la richiesta di creazione della password e salvare la credenziale della password dell'utente con il relativo provider della password, sostituire il TODO con il seguente codice :

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)
}
  • Ora hai salvato correttamente la credenziale della password con il provider della password dell'utente per autenticarla tramite password con un solo tocco.

5. Aggiungi la possibilità di eseguire l'autenticazione con una passkey o una password

Ora puoi utilizzarlo come metodo per eseguire l'autenticazione sicura per la tua app.

629001f4a778d4fb.png

Ottieni la sfida e altre opzioni da superare per la chiamata getPasskey()

Prima di chiedere all'utente di eseguire l'autenticazione, devi richiedere i parametri da passare nel json WebAuthn dal server, inclusa una verifica.

Hai già una risposta fittizia nei tuoi asset(AuthFromServer.txt) che restituisce questi parametri in questo codelab.

  • Nell'app, vai al metodo SignInFragment.kt, trova il metodo signInWithSavedCredentials in cui scriverai la logica per l'autenticazione tramite passkey o password salvata e consenti all'utente di accedere :
  • Seleziona il blocco "other" con un commento per chiamare la funzione createPasskey() e sostituiscila 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 una GetPublicKeyCredentialOption() con i parametri necessari per ottenere le credenziali dal tuo provider di credenziali.

SigninFragment.kt

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

val getPublicKeyCredentialOption =
   GetPublicKeyCredentialOption(fetchAuthJsonFromServer(), null)

Questo metodo fetchAuthJsonFromServer() legge la risposta del json di autenticazione dagli asset e restituisce il json di autenticazione per recuperare tutte le passkey associate a questo account utente.

Secondo parametro : clientDataHash. Un hash utilizzato per verificare l'identità del componente, impostato solo se hai impostato GetCredentialRequest.origin. Per l'app di esempio, questo valore è null.

Il terzo parametro è true se preferisci che l'operazione restituisca immediatamente quando non sono disponibili credenziali, invece di ricorrere al rilevamento delle credenziali remote; in caso contrario, è false (impostazione predefinita).

  • Trova il metodo fetchAuthJsonFromServer() e sostituisci TODO con il seguente codice per restituire il json e rimuovi l'istruzione di ritorno con 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 alcuni esempi di opzioni che dovresti ricevere:

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

La tabella seguente non è esaustiva, ma contiene i parametri importanti nel dizionario PublicKeyCredentialRequestOptions:

Parametri

Descrizioni

challenge

Una verifica generata dal server in un oggetto ArrayBuffer. Questa operazione è necessaria per evitare gli attacchi di ripetizione. Non accettare mai due volte la stessa sfida in una risposta. Consideralo un token CSRF.

rpId

Un ID parte soggetta a limitazioni è un dominio. Un sito web può specificare il proprio dominio o un suffisso registrabile. Questo valore deve corrispondere al parametro rp.id utilizzato al momento della creazione della passkey.

  • Successivamente, dovrai creare un oggetto PasswordOption() per recuperare tutte le password salvate salvate nel provider di password tramite l'API Credential Manager per questo account utente. All'interno del metodo getSavedCredentials(), 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 precedenti 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.
}

  • Devi passare le informazioni richieste a getCredential(). Viene utilizzato l'elenco delle opzioni per le credenziali e un contesto dell'attività per visualizzare le opzioni nel riquadro inferiore di quel contesto.
  • Una volta approvata la richiesta, sullo schermo verrà visualizzato un riquadro inferiore con l'elenco di tutte le credenziali create per l'account associato.
  • Ora gli utenti possono verificare la propria identità tramite la biometria, il blocco schermo e così via per autenticare la credenziale scelta.
  • Hai impostato il flagSignedInTramitePasskeys su true per indicare che stai accedendo tramite passkey. In caso contrario, è false.
  • Puoi gestire la visibilità delle visualizzazioni visualizzate e le eccezioni se la richiesta non riesce o non va a buon fine per qualche motivo. Qui i messaggi di errore vengono registrati e mostrati nell'app in una finestra di dialogo di errore. Puoi controllare i log completi degli errori tramite il comando di debug di Android Studio o adb.
  • Infine, devi completare il processo di registrazione inviando la credenziale della chiave pubblica al server e facendo accedere l'utente. L'app riceve un oggetto credenziali contenente una chiave pubblica che puoi inviare al server per l'autenticazione tramite passkey.

In questo caso abbiamo utilizzato un server fittizio, quindi restituiamo semplicemente true che indica che il server ha convalidato la chiave pubblica.

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 che indica che il server (simulativo) ha convalidato la chiave pubblica per l'uso futuro.
  • Una volta effettuato l'accesso, l'utente verrà reindirizzato alla schermata Home.

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 nell'oggetto PublicKeyCredential:

Parametri

Descrizioni

id

L'ID codificato Base64URL della credenziale passkey autenticata.

rawId

Una versione dell'oggetto ArrayBuffer dell'ID credenziale.

response.clientDataJSON

Un oggetto ArrayBuffer dei dati del client. Questo campo contiene informazioni quali la verifica e l'origine che il server RP deve verificare.

response.authenticatorData

Un oggetto ArrayBuffer dei dati dell'autenticatore. Questo campo contiene informazioni quali l'ID parte soggetta a limitazioni.

response.signature

Un oggetto ArrayBuffer della firma. Questo valore è il nucleo della credenziale e deve essere verificato sul server.

response.userHandle

Un oggetto ArrayBuffer contenente lo User-ID impostato al momento della creazione. Questo valore può essere utilizzato al posto dell'ID credenziale se il server deve scegliere i valori ID che utilizza o se il backend vuole evitare la creazione di un indice sugli ID delle credenziali.

Esegui l'app, vai alla pagina di accesso -> Accedi con passkey/password salvate e prova ad accedere usando le credenziali salvate.

Prova

Hai implementato la creazione di passkey, il salvataggio delle password in Gestore delle credenziali e l'autenticazione tramite passkey o password salvate usando l'API Credential Manager nella tua app per Android.

6. Complimenti!

Hai completato il codelab. Se vuoi controllare la risoluzione finale, puoi trovarla all'indirizzo https://github.com/android/identity-samples/tree/main/CredentialManager.

Poni eventuali domande su StackOverflow con un tag passkey.

Scopri di più