Autoriser l'accès aux données utilisateur Google

L'authentification permet d'établir l'identité d'une personne. Elle est généralement appelée inscription ou connexion d'un utilisateur. L'autorisation est le processus qui consiste à accorder ou refuser l'accès à des données ou des ressources. Par exemple, votre application demande à l'utilisateur d'autoriser l'accès à son compte Google Drive.

Les appels d'authentification et d'autorisation doivent être deux flux distincts en fonction des besoins de l'application.

Si votre application comporte des fonctionnalités qui peuvent utiliser les données des API Google, mais qui ne sont pas requises dans le cadre des fonctionnalités principales de votre application, vous devez concevoir votre application de manière à ce qu'elle puisse gérer correctement les cas où les données des API ne sont pas accessibles. Par exemple, vous pouvez masquer une liste de fichiers récemment enregistrés lorsque l'utilisateur n'a pas accordé l'accès à Drive.

Vous ne devez demander l'accès aux niveaux d'accès dont vous avez besoin pour accéder aux API Google que lorsque l'utilisateur effectue une action nécessitant l'accès à une API spécifique. Par exemple, vous devez demander l'autorisation d'accéder à Drive de l'utilisateur chaque fois qu'il appuie sur un bouton "Enregistrer dans Drive".

En séparant l'autorisation de l'authentification, vous pouvez éviter de submerger les nouveaux utilisateurs ou de les dérouter en leur demandant certaines autorisations.

Pour l'authentification, nous vous recommandons d'utiliser l'API Credential Manager. Pour autoriser les actions qui nécessitent d'accéder aux données utilisateur stockées par Google, nous vous recommandons d'utiliser AuthorizationClient.

Configurer votre projet

  1. Ouvrez votre projet dans ou créez-en un si vous n'en avez pas encore.
  2. Sur la page , assurez-vous que toutes les informations sont complètes et exactes.
    1. Assurez-vous que le nom, le logo et la page d'accueil de votre application sont corrects. Ces valeurs seront présentées aux utilisateurs sur l'écran d'autorisation Se connecter avec Google lors de l'inscription et sur l'écran Applications et services tiers.
    2. Assurez-vous d'avoir spécifié les URL des règles de confidentialité et des conditions d'utilisation de votre application.
  3. Dans , créez un ID client Android pour votre application si vous n'en avez pas déjà un. Vous devrez spécifier le nom du package et la signature SHA-1 de votre application.
    1. Accédez à .
    2. Cliquez sur Créer un client.
    3. Sélectionnez le type d'application Android.
  4. Dans , créez un ID client "Application Web" si vous ne l'avez pas déjà fait. Vous pouvez ignorer les champs "Origines JavaScript autorisées" et "URI de redirection autorisés" pour le moment. Cet ID client servira à identifier votre serveur de backend lorsqu'il communiquera avec les services d'authentification de Google.
    1. Accédez à .
    2. Cliquez sur Créer un client.
    3. Sélectionnez le type Application Web.

Déclarer des dépendances

Dans le fichier build.gradle de votre module, déclarez les dépendances à l'aide de la dernière version de la bibliothèque Google Identity Services.

dependencies {
  // ... other dependencies

  implementation "com.google.android.gms:play-services-auth:21.4.0"
}

Demander les autorisations requises par les actions de l'utilisateur

Chaque fois qu'un utilisateur effectue une action nécessitant un champ d'application supplémentaire, appelez AuthorizationClient.authorize(). Par exemple, si un utilisateur effectue une action qui nécessite d'accéder au stockage de son application Drive, procédez comme suit :

Kotlin

val requestedScopes: List<Scope> = listOf(DriveScopes.DRIVE_FILE)
val authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .build()

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequestBuilder.build())
    .addOnSuccessListener { authorizationResult ->
        if (authorizationResult.hasResolution()) {
            val pendingIntent = authorizationResult.pendingIntent
            // Access needs to be granted by the user
            startAuthorizationIntent.launchIntentSenderRequest.Builder(pendingIntent!!.intentSender).build()
        } else {
            // Access was previously granted, continue with user action
            saveToDriveAppFolder(authorizationResult);
        }
    }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }

Java

List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .build();

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequest)
    .addOnSuccessListener(authorizationResult -> {
        if (authorizationResult.hasResolution()) {
            // Access needs to be granted by the user
            startAuthorizationIntent.launch(
                new IntentSenderRequest.Builder(
                    authorizationResult.getPendingIntent().getIntentSender()
                ).build()
            );
        } else {
            // Access was previously granted, continue with user action
            saveToDriveAppFolder(authorizationResult);
        }
    })
    .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));

Lorsque vous définissez ActivityResultLauncher, gérez la réponse comme indiqué dans l'extrait suivant, où nous supposons que cela est fait dans un fragment. Le code vérifie que les autorisations requises ont bien été accordées, puis exécute l'action de l'utilisateur.

Kotlin

private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
): View? {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
            try {
                // extract the result
                val authorizationResult = Identity.getAuthorizationClient(requireContext())
                    .getAuthorizationResultFromIntent(activityResult.data)
                // continue with user action
                saveToDriveAppFolder(authorizationResult);
            } catch (ApiException e) {
                // log exception
            }
        }
}

Java

private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;

@Override
public View onCreateView(
    @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
startAuthorizationIntent =
    registerForActivityResult(
        new ActivityResultContracts.StartIntentSenderForResult(),
        activityResult -> {
            try {
            // extract the result
            AuthorizationResult authorizationResult =
                Identity.getAuthorizationClient(requireActivity())
                    .getAuthorizationResultFromIntent(activityResult.getData());
            // continue with user action
            saveToDriveAppFolder(authorizationResult);
            } catch (ApiException e) {
            // log exception
            }
        });
}

Si vous accédez aux API Google côté serveur, appelez la méthode getServerAuthCode() depuis AuthorizationResult pour obtenir un code d'autorisation que vous enverrez à votre backend pour l'échanger contre un jeton d'accès et d'actualisation. Pour en savoir plus, consultez Maintenir un accès continu aux données de l'utilisateur.

Révoquer les autorisations d'accès aux données ou ressources utilisateur

Pour révoquer un accès précédemment accordé, appelez AuthorizationClient.revokeAccess(). Par exemple, si l'utilisateur supprime son compte de votre application et que votre application avait précédemment obtenu l'accès à DriveScopes.DRIVE_FILE, utilisez le code suivant pour révoquer l'accès :

Kotlin

val requestedScopes: MutableList<Scope> = mutableListOf(DriveScopes.DRIVE_FILE)
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
    .setAccount(account)
    .setScopes(requestedScopes)
    .build()

Identity.getAuthorizationClient(activity)
    .revokeAccess(revokeAccessRequest)
    .addOnSuccessListener { Log.i(TAG, "Successfully revoked access") }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to revoke access", e) }

Java

List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
    .setAccount(account)
    .setScopes(requestedScopes)
    .build();

Identity.getAuthorizationClient(activity)
    .revokeAccess(revokeAccessRequest)
    .addOnSuccessListener(unused -> Log.i(TAG, "Successfully revoked access"))
    .addOnFailureListener(e -> Log.e(TAG, "Failed to revoke access", e));

Vider le cache de jetons

Les jetons d'accès OAuth sont mis en cache localement dès leur réception du serveur, ce qui accélère l'accès et réduit les appels réseau. Ces jetons sont automatiquement supprimés du cache à leur expiration, mais ils peuvent également devenir non valides pour d'autres raisons. Si vous recevez un IllegalStateException lorsque vous utilisez un jeton, videz le cache local pour vous assurer que la prochaine demande d'autorisation d'un jeton d'accès est envoyée au serveur OAuth. L'extrait suivant supprime invalidAccessToken du cache local :

Kotlin

Identity.getAuthorizationClient(activity)
    .clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
    .addOnSuccessListener { Log.i(TAG, "Successfully removed the token from the cache") }
    .addOnFailureListener{ e -> Log.e(TAG, "Failed to clear token", e) }

Java

Identity.getAuthorizationClient(activity)
    .clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
    .addOnSuccessListener(unused -> Log.i(TAG, "Successfully removed the token from the cache"))
    .addOnFailureListener(e -> Log.e(TAG, "Failed to clear the token cache", e));

Obtenir des informations sur l'utilisateur lors de l'autorisation

La réponse d'autorisation ne contient aucune information sur le compte utilisateur utilisé. Elle ne contient qu'un jeton pour les autorisations demandées. Par exemple, la réponse permettant d'obtenir un jeton d'accès à Google Drive d'un utilisateur ne révèle pas l'identité du compte sélectionné par l'utilisateur, même si elle peut être utilisée pour accéder aux fichiers sur le Drive de l'utilisateur. Pour obtenir des informations telles que le nom ou l'adresse e-mail de l'utilisateur, vous disposez des options suivantes :

  • Connectez l'utilisateur avec son compte Google à l'aide des API Credential Manager avant de demander l'autorisation. La réponse d'authentification du Gestionnaire d'identifiants inclut des informations sur l'utilisateur, telles que son adresse e-mail, et définit également le compte par défaut de l'application sur le compte sélectionné. Si nécessaire, vous pouvez suivre ce compte dans votre application. Une demande d'autorisation ultérieure utilise le compte par défaut et ignore l'étape de sélection du compte dans le flux d'autorisation. Pour utiliser un autre compte pour l'autorisation, consultez Autorisation à partir d'un compte autre que celui par défaut.

  • Dans votre demande d'autorisation, en plus des champs d'application souhaités (par exemple, Drive scope), demandez les champs d'application userinfo, profile et openid. Une fois le jeton d'accès renvoyé, récupérez les informations utilisateur en envoyant une requête HTTP GET au point de terminaison OAuth userinfo (https://www.googleapis.com/oauth2/v3/userinfo) à l'aide de votre bibliothèque HTTP préférée et en incluant le jeton d'accès que vous avez reçu dans l'en-tête, ce qui équivaut à la commande curl suivante :

    curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"
    

    La réponse est UserInfo, limitée aux niveaux d'accès demandés et mise en forme au format JSON.

Autorisation à partir d'un compte autre que celui par défaut

Si vous utilisez Credential Manager pour l'authentification et que vous exécutez AuthorizationClient.authorize(), le compte par défaut de votre application est défini sur celui sélectionné par votre utilisateur. Cela signifie que tous les appels d'autorisation ultérieurs utilisent ce compte par défaut. Pour forcer l'affichage du sélecteur de compte, déconnectez l'utilisateur de l'application à l'aide de l'API clearCredentialState() du Gestionnaire d'identifiants.

Maintenir un accès continu aux données de l'utilisateur

Si vous devez accéder aux données de l'utilisateur depuis votre application, appelez AuthorizationClient.authorize() une seule fois. Lors des sessions suivantes, et tant que les autorisations accordées ne sont pas supprimées par l'utilisateur, appelez la même méthode pour obtenir un jeton d'accès afin d'atteindre vos objectifs, sans aucune interaction de l'utilisateur. Si, en revanche, vous devez accéder aux données de l'utilisateur en mode hors connexion, depuis votre serveur backend, vous devez demander un autre type de jeton appelé "jeton d'actualisation".

Les jetons d'accès sont intentionnellement conçus pour être de courte durée et ont une durée de vie d'une heure. Si un jeton d'accès est intercepté ou piraté, sa durée de validité limitée minimise le risque d'utilisation abusive. Une fois expiré, le jeton devient invalide et toute tentative d'utilisation est rejetée par le serveur de ressources. Étant donné que les jetons d'accès sont de courte durée, les serveurs utilisent des jetons d'actualisation pour maintenir un accès continu aux données d'un utilisateur. Les jetons d'actualisation sont des jetons à longue durée de vie utilisés par un client pour demander un jeton d'accès éphémère au serveur d'autorisation lorsque l'ancien jeton d'accès a expiré, sans aucune interaction de l'utilisateur.

Pour obtenir un jeton d'actualisation, vous devez d'abord obtenir un code d'authentification (ou code d'autorisation) lors de l'étape d'autorisation dans votre application en demandant un "accès hors connexion", puis échanger le code d'authentification contre un jeton d'actualisation sur votre serveur. Il est essentiel de stocker les jetons d'actualisation de longue durée de manière sécurisée sur votre serveur, car ils peuvent être utilisés à plusieurs reprises pour obtenir de nouveaux jetons d'accès. Par conséquent, il est fortement déconseillé de stocker des jetons d'actualisation sur l'appareil pour des raisons de sécurité. Ils doivent plutôt être stockés sur les serveurs backend de l'application, où l'échange contre un jeton d'accès a lieu.

Une fois le code d'autorisation envoyé au serveur backend de votre application, vous pouvez l'échanger contre un jeton d'accès de courte durée sur le serveur et un jeton d'actualisation de longue durée en suivant les étapes du guide d'autorisation de compte. Cet échange ne doit avoir lieu que dans le backend de votre application.

Kotlin

// Ask for offline access during the first authorization request
val authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .requestOfflineAccess(serverClientId)
    .build()

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequest)
    .addOnSuccessListener { authorizationResult ->
        startAuthorizationIntent.launchIntentSenderRequest.Builder(
            pendingIntent!!.intentSender
        ).build()
    }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }

Java

// Ask for offline access during the first authorization request
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .requestOfflineAccess(serverClientId)
    .build();

Identity.getAuthorizationClient(getContext())
    .authorize(authorizationRequest)
    .addOnSuccessListener(authorizationResult -> {
        startAuthorizationIntent.launch(
            new IntentSenderRequest.Builder(
                authorizationResult.getPendingIntent().getIntentSender()
            ).build()
        );
    })
    .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize"));

L'extrait de code suivant suppose que l'autorisation est lancée à partir d'un fragment.

Kotlin

private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
): View? {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
            try {
                val authorizationResult = Identity.getAuthorizationClient(requireContext())
                    .getAuthorizationResultFromIntent(activityResult.data)
                // short-lived access token
                accessToken = authorizationResult.accessToken
                // store the authorization code used for getting a refresh token safely to your app's backend server
                val authCode: String = authorizationResult.serverAuthCode
                storeAuthCodeSafely(authCode)
            } catch (e: ApiException) {
                // log exception
            }
        }
}

Java

private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;

@Override
public View onCreateView(
    @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(
            new ActivityResultContracts.StartIntentSenderForResult(),
            activityResult -> {
                try {
                    AuthorizationResult authorizationResult =
                        Identity.getAuthorizationClient(requireActivity())
                            .getAuthorizationResultFromIntent(activityResult.getData());
                    // short-lived access token
                    accessToken = authorizationResult.getAccessToken();
                    // store the authorization code used for getting a refresh token safely to your app's backend server
                    String authCode = authorizationResult.getServerAuthCode()
                    storeAuthCodeSafely(authCode);
                } catch (ApiException e) {
                    // log exception
                }
            });
}