Authentifier les utilisateurs auprès de services OAuth2

Schéma de la logique des jetons d'authentification
Figure 1. Procédure pour obtenir un jeton d'authentification valide auprès du responsable de compte Android.

Pour accéder de manière sécurisée à un service en ligne, les utilisateurs doivent s'authentifier auprès du service et fournir une preuve de leur identité. Pour une application qui accède à un service tiers, le problème de sécurité est encore plus complexe. L'utilisateur doit non seulement être authentifié pour accéder au service, mais l'application doit également être autorisée à agir en son nom.

Le protocole OAuth2 est la méthode standard dans l'industrie pour gérer l'authentification auprès de services tiers. OAuth2 fournit une valeur unique, appelée jeton d'authentification, qui représente à la fois l'identité de l'utilisateur et l'autorisation d'une application à agir en son nom. Cette leçon explique comment se connecter à un serveur Google compatible avec OAuth2. Bien que les services Google soient utilisés à titre d'exemple, les techniques présentées fonctionneront pour tout service compatible avec le protocole OAuth2.

Le protocole OAuth2 est utile pour:

  • Obtenir l'autorisation de l'utilisateur d'accéder à un service en ligne à l'aide de son compte
  • S'authentifier auprès d'un service en ligne pour le compte de l'utilisateur
  • Gérer les erreurs d'authentification

Recueillir des informations

Pour commencer à utiliser OAuth2, vous devez connaître certaines informations spécifiques aux API concernant le service auquel vous essayez d'accéder:

  • URL du service auquel vous souhaitez accéder.
  • Le champ d'application de l'authentification, qui est une chaîne qui définit le type d'accès spécifique demandé par votre application. Par exemple, le champ d'application de l'authentification pour l'accès en lecture seule à Google Tasks est View your tasks, tandis que le champ d'application de l'authentification pour l'accès en lecture/écriture à Google Tasks est Manage your tasks.
  • Un ID client et un code secret du client, qui sont des chaînes qui identifient votre application auprès du service. Vous devez obtenir ces chaînes directement auprès du propriétaire du service. Google dispose d'un système en libre-service pour obtenir des ID client et des secrets.

Demander l'autorisation d'accéder à Internet

Pour les applications ciblant Android 6.0 (niveau d'API 23) ou version ultérieure, la méthode getAuthToken() ne nécessite aucune autorisation. Toutefois, pour effectuer des opérations sur le jeton, vous devez ajouter l'autorisation INTERNET à votre fichier manifeste, comme indiqué dans l'extrait de code suivant:

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

Demander un jeton d'authentification

Pour obtenir le jeton, appelez AccountManager.getAuthToken().

Attention : Étant donné que certaines opérations de compte peuvent impliquer une communication réseau, la plupart des méthodes AccountManager sont asynchrones. Cela signifie qu'au lieu d'effectuer tout le travail d'authentification dans une seule fonction, vous devez l'implémenter sous la forme d'une série de rappels.

L'extrait de code suivant montre comment utiliser une série de rappels pour obtenir le jeton:

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

Dans cet exemple, OnTokenAcquired est une classe qui implémente AccountManagerCallback. AccountManager appelle run() sur OnTokenAcquired avec un AccountManagerFuture contenant un Bundle. Si l'appel a réussi, le jeton se trouve dans Bundle.

Voici comment obtenir le jeton à partir de 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);
        ...
    }
}

Si tout se passe bien, Bundle contient un jeton valide dans la clé KEY_AUTHTOKEN et vous avez terminé.

Votre première demande de jeton d'authentification peut échouer pour plusieurs raisons:

  • Une erreur sur l'appareil ou le réseau a entraîné l'échec de AccountManager.
  • L'utilisateur a décidé de ne pas autoriser votre application à accéder au compte.
  • Les identifiants enregistrés ne suffisent pas pour accéder au compte.
  • Le jeton d'authentification mis en cache a expiré.

Les applications peuvent gérer facilement les deux premiers cas, généralement simplement en affichant un message d'erreur à l'utilisateur. Si le réseau est en panne ou si l'utilisateur a décidé de ne pas accorder l'accès, votre application ne peut pas faire grand-chose pour y remédier. Les deux derniers cas sont un peu plus compliqués, car les applications bien conçues sont censées gérer automatiquement ces défaillances.

Le troisième cas d'échec, lié à des identifiants insuffisants, est communiqué via le Bundle que vous recevez dans votre AccountManagerCallback (OnTokenAcquired de l'exemple précédent). Si Bundle inclut un Intent dans la clé KEY_INTENT, l'authentificateur vous indique qu'il doit interagir directement avec l'utilisateur avant de pouvoir vous fournir un jeton valide.

L'authentificateur peut renvoyer une Intent pour de nombreuses raisons. Il se peut que l'utilisateur se connecte à ce compte pour la première fois. Le compte de l'utilisateur a peut-être expiré et il doit se reconnecter, ou les identifiants stockés sont incorrects. Peut-être que le compte nécessite une authentification à deux facteurs ou qu'il doit activer l'appareil photo pour effectuer un scan rétinien. Peu importe la raison. Si vous voulez un jeton valide, vous devez déclencher Intent pour l'obtenir.

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;
        }
    }
}

Notez que l'exemple utilise startActivityForResult(). Vous pouvez donc capturer le résultat de l'Intent en implémentant onActivityResult() dans votre propre activité. C'est important: si vous ne capturez pas le résultat de la réponse Intent de l'authentificateur, il est impossible de savoir si l'utilisateur s'est correctement authentifié.

Si le résultat est RESULT_OK, cela signifie que l'authentificateur a mis à jour les identifiants stockés afin qu'ils soient suffisants pour le niveau d'accès que vous avez demandé. Vous devez donc appeler à nouveau AccountManager.getAuthToken() pour demander le nouveau jeton d'authentification.

Le dernier cas, où le jeton a expiré, n'est pas réellement un échec AccountManager. Le seul moyen de savoir si un jeton a expiré est de contacter le serveur. De plus, il serait coûteux et coûteux pour AccountManager de se connecter en permanence pour vérifier l'état de tous ses jetons. Il s'agit donc d'un échec qui ne peut être détecté que lorsqu'une application comme la vôtre tente d'utiliser le jeton d'authentification pour accéder à un service en ligne.

Se connecter au service en ligne

L'exemple ci-dessous montre comment se connecter à un serveur Google. Étant donné que Google utilise le protocole OAuth2 standard pour authentifier les requêtes, les techniques décrites ici sont applicables de manière générale. Gardez à l'esprit, cependant, que chaque serveur est différent. Vous devrez peut-être apporter de légers ajustements à ces instructions pour tenir compte de votre situation spécifique.

Les API Google exigent que vous fournissiez quatre valeurs par requête: la clé API, l'ID client, le code secret du client et la clé d'authentification. Les trois premiers proviennent du site Web de la console Google APIs. La dernière est la valeur de chaîne que vous avez obtenue en appelant AccountManager.getAuthToken(). Vous les transmettez au serveur Google dans le cadre d'une requête 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);

Si la requête renvoie un code d'erreur HTTP 401, votre jeton a été refusé. Comme indiqué dans la section précédente, la raison la plus courante est que le jeton a expiré. La solution est simple: appelez AccountManager.invalidateAuthToken() et répétez le processus d'acquisition du jeton une fois de plus.

Les jetons expirés étant très courants et leur résolution est si simple, de nombreuses applications supposent simplement que le jeton a expiré avant même de le demander. Si le renouvellement d'un jeton est une opération peu coûteuse pour votre serveur, vous pouvez appeler AccountManager.invalidateAuthToken() avant le premier appel à AccountManager.getAuthToken() et éviter d'avoir à demander un jeton d'authentification deux fois.