Eseguire l'autenticazione per i servizi OAuth2

Diagramma della logica del token di autenticazione
Figura 1. Procedura per ottenere un token di autenticazione valido dall'account manager Android.

Per accedere in modo sicuro a un servizio online, gli utenti devono eseguire l'autenticazione al servizio e fornire una prova della loro identità. Per un'applicazione che accede a un servizio di terze parti, il problema di sicurezza è ancora più complicato. Non solo l'utente deve essere autenticato per accedere al servizio, ma l'applicazione deve anche essere autorizzata ad agire per conto dell'utente.

Il protocollo OAuth2 è il metodo standard di settore per gestire l'autenticazione su servizi di terze parti. OAuth2 fornisce un singolo valore, chiamato token di autenticazione, che rappresenta sia l'identità dell'utente sia l'autorizzazione dell'applicazione ad agire per conto dell'utente. Questa lezione illustra la connessione a un server Google che supporta OAuth2. Anche se i servizi Google vengono utilizzati come esempio, le tecniche illustrate funzionano su qualsiasi servizio che supporti correttamente il protocollo OAuth2.

L'utilizzo di OAuth2 è consigliato per:

  • Ottenere l'autorizzazione dall'utente per accedere a un servizio online utilizzando il proprio account.
  • Autenticazione in un servizio online per conto dell'utente.
  • Gestione degli errori di autenticazione.

Raccogliere informazioni

Per iniziare a utilizzare OAuth2, devi conoscere alcune informazioni specifiche dell'API relative al servizio a cui vuoi accedere:

  • L'URL del servizio a cui vuoi accedere.
  • L'ambito di autenticazione, ovvero una stringa che definisce il tipo specifico di accesso richiesto dalla tua app. Ad esempio, l'ambito di autenticazione per l'accesso in sola lettura a Google Tasks è View your tasks, mentre l'ambito di autenticazione per l'accesso in lettura/scrittura a Google Tasks è Manage your tasks.
  • Un ID client e un client secret, ovvero stringhe che identificano l'app nel servizio. Devi ottenere queste stringhe direttamente dal proprietario del servizio. Google dispone di un sistema self-service per ottenimento di ID client e secret.

Richiedere l'autorizzazione a Internet

Per le app che hanno come target Android 6.0 (livello API 23) e versioni successive, il metodo getAuthToken() in sé non richiede autorizzazioni. Tuttavia, per eseguire operazioni sul token, devi aggiungere l'autorizzazione INTERNET al file manifest, come mostrato nello snippet di codice riportato di seguito:

<manifest ... >
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifest>

Richiedere un token di autenticazione

Per ottenere il token, chiama AccountManager.getAuthToken().

Attenzione: poiché alcune operazioni sull'account potrebbero comportare la comunicazione di rete, la maggior parte dei metodi AccountManager è asincrona. Ciò significa che, invece di eseguire tutte le operazioni di autenticazione in una funzione, devi implementarla come una serie di callback.

Lo snippet seguente mostra come utilizzare una serie di callback per ricevere il token:

Kotlin

val am: AccountManager = AccountManager.get(this)
val options = Bundle()

am.getAuthToken(
        myAccount_,                     // Account retrieved using getAccountsByType()
        "Manage your tasks",            // Auth scope
        options,                        // Authenticator-specific options
        this,                           // Your activity
        OnTokenAcquired(),              // Callback called when a token is successfully acquired
        Handler(OnError())              // Callback called if an error occurs
)

Java

AccountManager am = AccountManager.get(this);
Bundle options = new Bundle();

am.getAuthToken(
    myAccount_,                     // Account retrieved using getAccountsByType()
    "Manage your tasks",            // Auth scope
    options,                        // Authenticator-specific options
    this,                           // Your activity
    new OnTokenAcquired(),          // Callback called when a token is successfully acquired
    new Handler(new OnError()));    // Callback called if an error occurs

In questo esempio, OnTokenAcquired è una classe che implementa AccountManagerCallback. AccountManager chiama run() su OnTokenAcquired con un AccountManagerFuture che contiene Bundle. Se la chiamata ha esito positivo, il token si trova all'interno di Bundle.

Ecco come recuperare il token da Bundle:

Kotlin

private class OnTokenAcquired : AccountManagerCallback<Bundle> {

    override fun run(result: AccountManagerFuture<Bundle>) {
        // Get the result of the operation from the AccountManagerFuture.
        val bundle: Bundle = result.getResult()

        // The token is a named value in the bundle. The name of the value
        // is stored in the constant AccountManager.KEY_AUTHTOKEN.
        val token: String = bundle.getString(AccountManager.KEY_AUTHTOKEN)
    }
}

Java

private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
    @Override
    public void run(AccountManagerFuture<Bundle> result) {
        // Get the result of the operation from the AccountManagerFuture.
        Bundle bundle = result.getResult();

        // The token is a named value in the bundle. The name of the value
        // is stored in the constant AccountManager.KEY_AUTHTOKEN.
        String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
        ...
    }
}

Se tutto va bene, Bundle contiene un token valido nella chiave KEY_AUTHTOKEN ed è tutto pronto.

La prima richiesta di un token di autenticazione potrebbe non andare a buon fine per diversi motivi:

  • Un errore nel dispositivo o nella rete ha causato l'errore di AccountManager.
  • L'utente ha deciso di non concedere alla tua app l'accesso all'account.
  • Le credenziali dell'account memorizzate non sono sufficienti per ottenere l'accesso all'account.
  • Il token di autenticazione memorizzato nella cache è scaduto.

Le applicazioni possono gestire i primi due casi in modo banale, di solito mostrando semplicemente un messaggio di errore all'utente. Se la rete non è disponibile o l'utente ha deciso di non concedere l'accesso, la tua applicazione non può fare molto. Gli ultimi due casi sono un po' più complicati, perché ci si aspetta che le applicazioni ben strutturate gestiscano questi errori automaticamente.

Il terzo caso di errore, se le credenziali non sono sufficienti, viene comunicato tramite il Bundle che ricevi in AccountManagerCallback (OnTokenAcquired dall'esempio precedente). Se Bundle include un Intent nella chiave KEY_INTENT, l'autenticatore ti comunica che deve interagire direttamente con l'utente prima di poterti fornire un token valido.

Potrebbero esserci molti motivi per cui l'autenticatore restituisce un Intent. Potrebbe essere la prima volta che l'utente accede a questo account. Forse l'account dell'utente è scaduto e l'utente deve accedere di nuovo oppure le credenziali memorizzate non sono corrette. Forse l'account richiede l'autenticazione a due fattori o deve attivare la fotocamera per eseguire una scansione della retina. Non importa quale sia il motivo. Se vuoi un token valido, devi disattivare Intent per ottenerlo.

Kotlin

private inner class OnTokenAcquired : AccountManagerCallback<Bundle> {

    override fun run(result: AccountManagerFuture<Bundle>) {
        val launch: Intent? = result.getResult().get(AccountManager.KEY_INTENT) as? Intent
        if (launch != null) {
            startActivityForResult(launch, 0)
        }
    }
}

Java

private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
    @Override
    public void run(AccountManagerFuture<Bundle> result) {
        ...
        Intent launch = (Intent) result.getResult().get(AccountManager.KEY_INTENT);
        if (launch != null) {
            startActivityForResult(launch, 0);
            return;
        }
    }
}

Tieni presente che l'esempio utilizza startActivityForResult(), in modo che tu possa acquisire il risultato di Intent implementando onActivityResult() nella tua attività. Questo è importante: se non acquisisci il risultato della risposta dell'autenticatore Intent, è impossibile capire se l'utente ha eseguito correttamente l'autenticazione.

Se il risultato è RESULT_OK, l'autenticatore ha aggiornato le credenziali archiviate in modo che siano sufficienti per il livello di accesso richiesto e dovresti chiamare di nuovo AccountManager.getAuthToken() per richiedere il nuovo token di autenticazione.

L'ultimo caso, in cui il token è scaduto, non è in realtà un errore AccountManager. L'unico modo per scoprire se un token è scaduto è contattare il server e sarebbe sprecato e costoso per AccountManager accedere continuamente a internet per controllare lo stato di tutti i suoi token. Quindi, si tratta di un errore che può essere rilevato solo quando un'applicazione come la tua tenta di utilizzare il token di autenticazione per accedere a un servizio online.

Connettersi al servizio online

L'esempio seguente mostra come connettersi a un server Google. Poiché Google utilizza il protocollo OAuth2 standard di settore per autenticare le richieste, le tecniche illustrate qui sono applicabili a tutti. Tuttavia, tieni presente che ogni server è diverso. Potresti dover apportare piccole modifiche a queste istruzioni per tenere conto della tua situazione specifica.

Le API di Google richiedono di fornire quattro valori per ogni richiesta: la chiave API, l'ID client, il client secret e la chiave di autenticazione. I primi tre provengono dal sito web della console API di Google. L'ultimo è il valore stringa ottenuto chiamando AccountManager.getAuthToken(). che passi al server di Google come parte di una richiesta HTTP.

Kotlin

val url = URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=$your_api_key")
val conn = url.openConnection() as HttpURLConnection
conn.apply {
    addRequestProperty("client_id", your client id)
    addRequestProperty("client_secret", your client secret)
    setRequestProperty("Authorization", "OAuth $token")
}

Java

URL url = new URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=" + your_api_key);
URLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("client_id", your client id);
conn.addRequestProperty("client_secret", your client secret);
conn.setRequestProperty("Authorization", "OAuth " + token);

Se la richiesta restituisce un codice di errore HTTP 401, il token è stato negato. Come menzionato nell'ultima sezione, il motivo più comune è la scadenza del token. La soluzione è semplice: chiama AccountManager.invalidateAuthToken() e ripeti ancora una volta il processo di acquisizione del token.

I token scaduti sono molto frequenti e la loro correzione è molto semplice, pertanto molte applicazioni presuppongono che il token sia scaduto prima ancora di richiederlo. Se il rinnovo di un token è un'operazione economica per il tuo server, potresti preferire chiamare AccountManager.invalidateAuthToken() prima della prima chiamata a AccountManager.getAuthToken() e evitare di richiedere un token di autenticazione due volte.