Revisione del quarto trimestre del 2024: 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 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

  1. 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
  1. 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:

  1. Avvia l'app.
  2. 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.

7a6fe80f4cf877a8.jpeg

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.

1f4c50daa2551f1.jpeg

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 un CreatePublicKeyCredentialRequest() 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 valore userId 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

challenge

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.

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 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.

user.displayName

Questo campo è un nome facoltativo e più intuitivo per l'account.

rp.id

La persona giuridica della terza parte attendibile corrisponde ai dettagli della tua applicazione. Ha i seguenti attributi:

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

pubKeyCredParams

Elenco di algoritmi e tipi di chiavi consentiti. Questo elenco deve contenere almeno un elemento.

excludeCredentials

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 transports, 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 è necessario. Imposta questo valore su platform. Ciò indica che vuoi un'app di autenticazione integrata 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 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)
}
  • 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.

1ea8ace66135de1e.png

  1. 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() restituisce true per indicare che il server simulato ha salvato la chiave pubblica per un uso futuro.
  • Imposta il flag setSignedInThroughPasskeys su true.
  • 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

id

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.

rawId

Una versione dell'oggetto ArrayBuffer dell'ID credenziale.

response.clientDataJSON

Un oggetto ArrayBuffer con dati del client codificati.

response.attestationObject

Un oggetto attestazione codificato in ArrayBuffer. Contiene informazioni importanti, come un ID RP, flag e una chiave pubblica.

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 a 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, 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.

76e81460b26f9798.png

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

challenge

Una verifica generata dal server in un oggetto ArrayBuffer. Questo è necessario per prevenire gli attacchi di replay. Non accettare mai la stessa sfida due volte in una risposta. Trattalo come un token CSRF.

rpId

Un ID RP è un dominio. Un sito web può specificare il proprio dominio o un sufisso registrabile. Questo valore deve corrispondere al parametro rp.id utilizzato durante la creazione della passkey.

  • 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 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 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 flag setSignedInThroughPasskeys su true. In caso contrario, impostalo su false.

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

id

L'ID con codifica Base64URL della credenziale della passkey autenticata.

rawId

Una versione dell'oggetto ArrayBuffer dell'ID credenziale.

response.clientDataJSON

Un oggetto ArrayBuffer di dati del cliente. Questo campo contiene informazioni, come la verifica e l'origine che il server RP deve verificare.

response.authenticatorData

Un oggetto ArrayBuffer di dati dell'autenticatore. Questo campo contiene informazioni come l'ID RP.

response.signature

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

response.userHandle

Un oggetto ArrayBuffer che contiene l'ID utente impostato al momento della creazione. Questo valore può essere utilizzato al posto dell'ID credenziale se il server deve scegliere i valori ID da utilizzare o se il backend vuole evitare la creazione di un indice sugli ID credenziale.

  • 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.

Scopri di più