Revisione 2025 T4: 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 sfide in termini di sicurezza e usabilità.

Le password sono ampiamente utilizzate, ma…

  • Facili da dimenticare
  • Gli utenti devono avere le conoscenze necessarie per creare password efficaci.
  • Facili da usare per il phishing, la raccolta e la riproduzione da parte degli autori degli attacchi.

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 per le 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 l'utente ha eseguito l'accesso. Prima che un utente possa accedere con una passkey, questa deve essere creata, associata a un account utente e la relativa chiave pubblica deve essere memorizzata su un server.

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

  • Registrazione : utilizzando passkey e password.
  • Accedi : utilizzando le passkey e la password salvata.

Prerequisiti

  • Conoscenza di base di come eseguire le 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 con password tramite l'API Credential Manager).
  • Dispositivo preferibilmente con un sensore biometrico.
  • Assicurati di registrare un blocco schermo (biometrico o di altro tipo).
  • Versione del plug-in Kotlin : 1.8.10

2. Configurazione

Questa app di esempio richiede un collegamento delle risorse digitali a un sito web affinché Credential Manager possa convalidare il collegamento e procedere ulteriormente, quindi l'ID rp utilizzato nelle risposte simulate proviene da un server di terze parti simulato. Se vuoi provare la tua risposta simulata, prova ad aggiungere il dominio dell'app e non dimenticare di completare il collegamento delle risorse digitali come indicato qui.

Utilizza lo stesso debug.keystore menzionato nel progetto per creare varianti di debug e release per verificare il collegamento delle risorse digitali del nome pacchetto e di SHA sul server di test. (Questo passaggio è già stato eseguito per te per l'app di esempio in build.gradle).

  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 vedere come funziona lo stato iniziale dell'app, segui questi passaggi:

  1. Avvia l'app.
  2. Viene visualizzata una schermata principale con un pulsante di registrazione e accesso. Questi pulsanti non fanno ancora nulla, ma ne abiliteremo la funzionalità nelle sezioni successive.

7a6fe80f4cf877a8.jpeg

3. Aggiungere la possibilità di registrarsi utilizzando le passkey

Quando si registra un nuovo account su un'app per Android che utilizza l'API Credential Manager, l'utente può 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 richiedere all'utente di inserire la password ogni volta.

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

Registrarsi con una passkey

Il codice all'interno di CredentialManager/app/src/main/java/com/google/credentialmanager/sample/SignUpScreen.kt definisce un campo di testo "username" e un pulsante per registrarsi con una passkey.

1f4c50daa2551f1.jpeg

Definisci la lambda createCredential() da utilizzare nei modelli di visualizzazione

Gli oggetti Credential Manager richiedono il passaggio di un Activity, associato a una schermata. Tuttavia, le operazioni di Credential Manager vengono in genere attivate in View Models e non è consigliabile fare riferimento alle attività all'interno di View Models. Pertanto, definiamo le funzioni di Credential Manager in un file separato CredentialManagerUtil.kt e le facciamo riferimento nelle schermate appropriate, che poi le passano ai relativi View Model come callback tramite funzioni lambda.

Individua il commento TODO nella funzione createCredential() in CredentialManagerUtil.kt e chiama la funzione CredentialManager.create():

CredentialManagerUtil.kt

suspend fun createCredential(
    activity: Activity,
    request: CreateCredentialRequest
): CreateCredentialResponse {
    TODO("Create a CredentialManager object and call createCredential() with a CreateCredentialRequest")
    val credentialManager = CredentialManager.create(activity)
    return credentialManager.createCredential(activity, request)
}

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 trasmettere all'API Credential Manager durante la chiamata createCredential().

Hai già una risposta simulata negli asset del tuo progetto, chiamata RegFromServer.txt, che restituisce i parametri necessari in questo codelab.

  • Nella tua app, vai a SignUpViewModel.kt. Trova il metodo signUpWithPasskeys in cui scriverai la logica per creare una passkey e consentire l'accesso all'utente. Puoi trovare il metodo nella stessa classe.
  • Individua il blocco di commenti TODO in create a CreatePublicKeyCredentialRequest() e sostituiscilo con il seguente codice:

SignUpViewModel.kt

TODO("Create a CreatePublicKeyCredentialRequest() with necessary registration json from server")
    val request = CreatePublicKeyCredentialRequest(
        jsonProvider.fetchRegistrationJson()
            .replace("<userId>", getEncodedUserId())
            .replace("<userName>", _username.value)
            .replace("<userDisplayName>", _username.value)
            .replace("<challenge>", getEncodedChallenge())
    )

Il metodo jsonProvider.fetchRegistrationJsonFromServer() legge una risposta JSON PublicKeyCredentialCreationOptions del server emulato dagli asset e restituisce il JSON di registrazione da trasmettere durante la creazione della passkey. Sostituiamo alcuni valori dei segnaposto con le voci degli utenti della nostra app e alcuni campi simulati:

  • Questo JSON è incompleto e contiene 4 campi che devono essere sostituiti.
  • L'ID utente 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 tuo codice.

Una risposta del server reale PublicKeyCredentialCreationOptions 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 per renderne impossibile l'individuazione. Deve avere una lunghezza di almeno 16 byte. Questo campo è 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 funzionerà bene.

user.name

Questo campo deve contenere un identificatore univoco per l'account che l'utente riconoscerà, ad esempio il suo indirizzo email o 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

L'entità Relying Party corrisponde ai dettagli della tua applicazione. Ha i seguenti attributi:

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

pubKeyCredParams

Elenco degli algoritmi e dei 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 in un singolo autenticatore, puoi ignorare questi dispositivi. Il membro 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, se non deve esserlo o se non è necessario farlo. Imposta questo valore su platform. Ciò indica che vuoi 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. Una volta creato un CreatePublicKeyCredentialRequest(), devi chiamare createCredential() con la richiesta creata.

SignUpViewModel.kt

try {
   TODO("Call createCredential() with createPublicKeyCredentialRequest")
   createCredential(request)
   TODO("Complete the registration process after sending public key credential to your server and let the user in")

} catch (e: CreateCredentialException) {
   handlePasskeyFailure(e)
}

  • Gestisci la visibilità delle visualizzazioni sottoposte a rendering e gestisci le eccezioni se la richiesta non va a buon fine o non riesce 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 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 di chiave pubblica al server, che la registra per l'utente corrente.

Qui abbiamo utilizzato un server simulato, quindi restituiamo semplicemente il valore true che indica che il server ha salvato la chiave pubblica registrata per scopi di autenticazione e convalida futuri. Per la tua implementazione, puoi scoprire di più sulla registrazione delle passkey lato server.

All'interno del metodo signUpWithPasskeys(), trova il commento pertinente e sostituiscilo con il seguente codice:

SignUpViewModel.kt

try {
    createCredential(request)
    TODO("Complete the registration process after sending public key credential to your server and let the user in")
registerResponse()
    DataProvider.setSignedInThroughPasskeys(true)
    _navigationEvent.emit(NavigationEvent.NavigateToHome(signedInWithPasskeys = true))
} catch (e: CreateCredentialException) {
   handlePasskeyFailure(e)
}
  • registerResponse() restituisce true, a indicare che il server di simulazione 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 vero PublicKeyCredential 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 spiega alcuni dei parametri importanti in un oggetto PublicKeyCredential:

Parametri

Descrizioni

id

Un ID con codifica Base64URL della passkey creata. Questo ID aiuta il browser a determinare se è presente una passkey corrispondente nel dispositivo al momento dell'autenticazione. Questo valore deve essere memorizzato nel database sul backend.

rawId

Una versione dell'oggetto ArrayBuffer dell'ID credenziali.

response.clientDataJSON

Un oggetto ArrayBuffer codifica i dati del cliente.

response.attestationObject

Un oggetto di attestazione codificato 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 di credenziali

In questa app, all'interno della schermata di registrazione, è già implementata una registrazione con nome utente e password a scopo dimostrativo.

Per salvare la credenziale della password utente con il relativo fornitore di password, implementerai un CreatePasswordRequest da passare a createCredential() per salvare la password.

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

SignUpViewModel.kt

TODO("CreatePasswordRequest with entered username and password")
    val passwordRequest = CreatePasswordRequest(_username.value, _password.value)

  • Poi, crea una credenziale con una richiesta di creazione della password e salva la credenziale della password dell'utente con il relativo fornitore di password. Quindi, accedi come utente. Rileviamo le eccezioni che si verificano in questo flusso in modo più generico. Sostituisci TODO con il seguente codice:

SignUpViewModel.kt

TODO("Create credential with created password request and log the user in")
    try {
        createCredential(passwordRequest)
        simulateServerDelayAndLogIn()
    } catch (e: Exception) {
        val errorMessage = "Exception Message : " + e.message
        Log.e("Auth", errorMessage)
        _passwordCreationError.value = errorMessage
        _isLoading.value = false
    }

Ora hai salvato correttamente la credenziale della password con il fornitore di password dell'utente per l'autenticazione con una password con un solo tocco.

5. Aggiungere la possibilità di autenticarsi con una passkey o una password

Ora puoi utilizzarlo per autenticarti in modo sicuro nella tua app.

76e81460b26f9798.png

Definisci la lambda getCredential() da utilizzare nei modelli di visualizzazione

Come in precedenza, chiameremo getCredential() di Credential Manager in un file separato CredentialManagerUtil.kt per fare riferimento nelle schermate appropriate e passare ai relativi View Model come callback tramite le funzioni lambda.

Individua il commento TODO nella funzione getCredential() in CredentialManagerUtil.kt e chiama la funzione CredentialManager.get():

suspend fun getCredential(
    activity: Activity,
    request: GetCredentialRequest
): GetCredentialResponse {
    TODO("Create a CredentialManager object and call getCredential() with a GetCredentialRequest")
    val credentialManager = CredentialManager.create(activity)
    return credentialManager.getCredential(activity, request)
}

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 WebAuthn JSON dal server, inclusa una sfida.

Hai già una risposta simulata nelle risorse (AuthFromServer.txt) che restituisce questi parametri in questo codelab.

  • Nella tua app, vai a SignInViewModel.kt, trova il metodo signInWithSavedCredentials in cui scriverai la logica per l'autenticazione tramite passkey o password salvata e consenti l'accesso all'utente:
  • Crea un oggetto GetPublicKeyCredentialOption() con i parametri necessari per ottenere le credenziali dal tuo fornitore di credenziali.

SignInViewModel.kt

TODO("Create a GetPublicKeyCredentialOption() with necessary authentication json from server")
    val getPublicKeyCredentialOption =
        GetPublicKeyCredentialOption(jsonProvider.fetchAuthJson(), 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 relying party. Imposta questo valore solo se hai impostato GetCredentialRequest.origin. Per l'app di esempio, questo valore è impostato su null.

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 impedire gli attacchi di replay. Non accettare mai due volte la stessa sfida in una risposta. Consideralo un token CSRF.

rpId

Un ID RP è 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, devi creare un oggetto PasswordOption() per recuperare tutte le password salvate nel tuo fornitore di password tramite l'API Credential Manager per questo account utente. All'interno del metodo getSavedCredentials(), trova TODO e sostituiscilo con quanto segue:

SigninViewModel.kt

TODO("Create a PasswordOption to retrieve all the associated user's password")

val getPasswordOption = GetPasswordOption()

Combinali in un GetCredentialRequest.

SigninViewModel.kt

TODO("Combine requests into a GetCredentialRequest")
    val request = GetCredentialRequest(
        listOf(
            getPublicKeyCredentialOption,
            getPasswordOption
        )
    )

Recupera le credenziali

Successivamente, devi chiamare la richiesta getCredential() con tutte le opzioni precedenti per recuperare le credenziali associate:

SignInViewModel.kt

try {
    TODO("Call getCredential() with required credential options")
    val result = getCredential(request)

    val data = when (result.credential) {
        is PublicKeyCredential -> {
            val cred = result.credential as PublicKeyCredential
            DataProvider.setSignedInThroughPasskeys(true)
            "Passkey: ${cred.authenticationResponseJson}"
        }

        is PasswordCredential -> {
            val cred = result.credential as PasswordCredential
            DataProvider.setSignedInThroughPasskeys(false)
            "Got Password - User:${cred.id} Password: ${cred.password}"
        }

        is CustomCredential -> {
            //If you are also using any external sign-in libraries, parse them here with the utility functions provided.
            null
        }

        else -> null
    }

    TODO("Complete the authentication process after validating the public key credential to your server and let the user in.")
    } catch (e: Exception) {
        Log.e("Auth", "getCredential failed with exception: " + e.message.toString())
        _signInError.value =
            "An error occurred while authenticating: " + e.message.toString()
    } finally {
        _isLoading.value = false
    }
  • Trasmetti le informazioni richieste a getCredential(). Viene visualizzato l'elenco delle opzioni delle credenziali e un contesto di attività per visualizzare le opzioni nel foglio inferiore in quel contesto.
  • Una volta che la richiesta è andata a buon fine, sullo schermo verrà visualizzato un foglio inferiore 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 è un PublicKeyCredential, imposta il flag setSignedInThroughPasskeys su true. In caso contrario, impostalo su false.

Il seguente snippet di codice include un esempio di oggetto PublicKeyCredential:

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

La seguente tabella non è esaustiva, ma contiene i parametri importanti nell'oggetto PublicKeyCredential:

Parametri

Descrizioni

id

L'ID con codifica Base64URL della credenziale passkey autenticata.

rawId

Una versione dell'oggetto ArrayBuffer dell'ID credenziali.

response.clientDataJSON

Un oggetto ArrayBuffer di dati del cliente. Questo campo contiene informazioni, come la sfida 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 fulcro 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 credenziali.

  • Infine, devi completare la procedura di autenticazione. Normalmente, dopo che l'utente completa l'autenticazione con passkey, l'app invia al server una credenziale di chiave pubblica contenente un'asserzione di autenticazione, che verifica l'asserzione e autentica l'utente.

Qui abbiamo utilizzato un server simulato, quindi restituiamo semplicemente true per indicare che il server ha verificato l'asserzione. Puoi scoprire di più sull'autenticazione passkey lato server per la tua implementazione.

All'interno del metodo signInWithSavedCredentials(), trova il commento pertinente e sostituiscilo con il seguente codice:

SignInViewModel.kt

TODO("Complete the authentication process after validating the public key credential to your server and let the user in.")
    if (data != null) {
        sendSignInResponseToServer()
        _navigationEvent.emit(NavigationEvent.NavigateToHome(signedInWithPasskeys = DataProvider.isSignedInThroughPasskeys()))
    }
  • sendSigninResponseToServer() restituisce true, indicando che il server (simulato) ha convalidato la chiave pubblica per un utilizzo futuro.
  • Una volta eseguito l'accesso, reindirizza l'utente alla schermata Home.

Esegui l'app e vai ad Accedi > Accedi con le passkey/password salvate e prova ad accedere utilizzando le credenziali salvate.

Prova

Hai implementato la creazione di passkey, il salvataggio della password in Credential Manager e l'autenticazione tramite passkey o password salvata utilizzando l'API Credential Manager nella tua app per Android.

6. Complimenti!

Hai completato questo codelab. Se vuoi controllare la soluzione finale, disponibile all'indirizzo https://github.com/android/identity-samples/tree/main/CredentialManager

In caso di domande, pubblicale su Stack Overflow con il tag passkey.

Scopri di più