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 autenticarsi 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 è la soluzione 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. Sebbene i servizi Google vengano utilizzati come esempio, le tecniche dimostrate funzioneranno su qualsiasi servizio che supporta correttamente il protocollo OAuth2.

L'utilizzo di OAuth2 è consigliato per:

  • Ottenere l'autorizzazione dell'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 stai tentando di 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 di 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 la tua app nel servizio. Devi ottenere queste stringhe direttamente dal proprietario del servizio. Google dispone di un sistema self-service per ottenere 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() stesso non richiede autorizzazioni. Tuttavia, per eseguire operazioni sul token, devi aggiungere l'autorizzazione INTERNET al file manifest, come mostrato nel seguente snippet di codice:

<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 dell'account potrebbero comportare la comunicazione di rete, la maggior parte dei metodi AccountManager è asincrona. Ciò significa che, invece di svolgere tutte le operazioni di autenticazione in una sola 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 contenente un Bundle. Se la chiamata ha esito positivo, il token si trova all'interno di Bundle.

Ecco come ottenere 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 tua 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 il mancato funzionamento di AccountManager.
  • L'utente ha deciso di non concedere alla tua app l'accesso all'account.
  • Le credenziali dell'account memorizzato 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, l'applicazione non può fare molto. Gli ultimi due casi sono un po' più complicati, perché si prevede che le applicazioni ben funzionate gestiscano questi errori automaticamente.

Il terzo caso di errore, se le credenziali non sono sufficienti, viene comunicato tramite il Bundle che hai ricevuto nel tuo 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 che possa fornirti un token valido.

I motivi per cui l'autenticatore potrebbe restituire un Intent potrebbero essere diversi. Potrebbe essere la prima volta che l'utente accede a questo account. Forse l'account dell'utente è scaduto e l'utente deve eseguire di nuovo l'accesso oppure le credenziali archiviate 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 di che cosa si tratta. Se vuoi un token valido, devi attivare 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(), per consentirti di acquisire il risultato di Intent implementando onActivityResult() nella tua attività. Questo è importante: se non acquisisci il risultato dalla 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. Devi 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 dispendioso e costoso per AccountManager accedere continuamente a internet per controllare lo stato di tutti i suoi token. Si tratta di un errore che può essere rilevato soltanto 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 di Google. Poiché Google utilizza il protocollo OAuth2 standard di settore per autenticare le richieste, le tecniche illustrate qui sono applicabili in modo ampio. 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, significa che il token è stato negato. Come accennato nell'ultima sezione, il motivo più comune è che il token è scaduto. La soluzione è semplice: chiama AccountManager.invalidateAuthToken() e ripeti ancora una volta la procedura di acquisizione dei token.

Poiché i token scaduti sono un evento molto comune e correggerli è così semplice, molte applicazioni presumono che il token sia scaduto prima ancora di richiederlo. Se il rinnovo di un token per il tuo server è un'operazione economica, potresti preferire chiamare AccountManager.invalidateAuthToken() prima della prima chiamata al numero AccountManager.getAuthToken() e evitare di richiedere un token di autenticazione due volte.