Autoriza el acceso a los datos del usuario de Google

La autenticación establece quién es alguien y se conoce comúnmente como registro o acceso del usuario. La autorización es el proceso de otorgar o rechazar el acceso a los datos o recursos. Por ejemplo, tu app solicita el consentimiento del usuario para acceder a su Google Drive.

Las llamadas de autenticación y autorización deben ser dos flujos separados y distintos según las necesidades de la app.

Si tu app tiene funciones que pueden usar datos de las APIs de Google, pero no son obligatorias como parte de las funciones principales de la app, debes diseñarla para que pueda controlar correctamente los casos en los que no se puede acceder a los datos de las APIs. Por ejemplo, puedes ocultar una lista de archivos guardados recientemente cuando el usuario no haya otorgado acceso a Drive.

Solo debes solicitar acceso a los permisos que necesitas para acceder a las APIs de Google cuando el usuario realice una acción que requiera acceso a una API en particular. Por ejemplo, debes solicitar permiso para acceder al Drive del usuario cada vez que este presione el botón "Guardar en Drive".

Si separas la autorización de la autenticación, puedes evitar abrumar a los usuarios nuevos o confundirlos sobre por qué se les solicitan ciertos permisos.

Para la autenticación, recomendamos usar la API de Credential Manager. Para autorizar acciones que necesitan acceso a los datos del usuario almacenados por Google, recomendamos usar AuthorizationClient.

Configura tu proyecto

  1. Abre tu proyecto en o crea uno si aún no tienes uno.
  2. En , asegúrate de que toda la información esté completa y sea precisa.
    1. Asegúrate de que tu app tenga asignados el nombre, el logotipo y la página principal correctos. Estos valores se presentarán a los usuarios en la pantalla de consentimiento de Acceder con Google durante el registro y en la pantalla de Apps y servicios de terceros.
    2. Asegúrate de haber especificado las URLs de la política de privacidad y las condiciones del servicio de tu app.
  3. En , crea un ID de cliente de Android para tu app si aún no tienes uno. Deberás especificar el nombre del paquete y la firma SHA-1 de tu app.
    1. Ve a .
    2. Haz clic en Crear cliente.
    3. Selecciona el tipo de aplicación Android.
  4. En , crea un nuevo ID de cliente de "Aplicación web" si aún no lo hiciste. Por el momento, puedes ignorar los campos "Orígenes de JavaScript autorizados" y "URI de redireccionamiento autorizados". Este ID de cliente se usará para identificar tu servidor de backend cuando se comunique con los servicios de autenticación de Google.
    1. Ve a .
    2. Haz clic en Crear cliente.
    3. Selecciona el tipo Aplicación web.

Cómo declarar dependencias

En el archivo build.gradle de tu módulo, declara dependencias con la versión más reciente de la biblioteca de Google Identity Services.

dependencies {
  // ... other dependencies

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

Solicita los permisos que requieren las acciones del usuario

Cada vez que un usuario realice una acción que requiera un alcance adicional, llama a AuthorizationClient.authorize(). Por ejemplo, si un usuario realiza una acción que requiere acceso al almacenamiento de la app de Drive, haz lo siguiente:

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

Cuando definas ActivityResultLauncher, controla la respuesta como se muestra en el siguiente fragmento, en el que suponemos que se realiza en un fragmento. El código verifica que los permisos necesarios se hayan otorgado correctamente y, luego, lleva a cabo la acción del usuario.

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 accedes a las APIs de Google en el servidor, llama al método getServerAuthCode() desde AuthorizationResult para obtener un código de autorización que enviarás a tu backend para intercambiarlo por un token de acceso y actualización. Para obtener más información, consulta Cómo mantener el acceso continuo a los datos del usuario.

Revoca los permisos para acceder a los datos o recursos del usuario

Para revocar el acceso otorgado previamente, llama a AuthorizationClient.revokeAccess(). Por ejemplo, si el usuario quita su cuenta de tu app y esta tenía acceso a DriveScopes.DRIVE_FILE, usa el siguiente código para revocar el acceso:

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

Borra la caché de tokens

Los tokens de acceso de OAuth se almacenan en caché de forma local cuando se reciben del servidor, lo que acelera el acceso y reduce las llamadas de red. Estos tokens se borran automáticamente de la caché cuando vencen, pero también pueden dejar de ser válidos por otros motivos. Si recibes un IllegalStateException cuando usas un token, borra la caché local para asegurarte de que la próxima solicitud de autorización para un token de acceso se dirija al servidor de OAuth. El siguiente fragmento quita el invalidAccessToken de la caché 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));

Obtén información del usuario durante la autorización

La respuesta de autorización no contiene información sobre la cuenta de usuario que se usó; solo contiene un token para los alcances solicitados. Por ejemplo, la respuesta para obtener un token de acceso a Google Drive de un usuario no revela la identidad de la cuenta que seleccionó el usuario, aunque se puede usar para acceder a los archivos en la unidad del usuario. Para obtener información como el nombre o el correo electrónico del usuario, tienes las siguientes opciones:

  • Haz que el usuario acceda con su Cuenta de Google a través de las APIs de Credential Manager antes de solicitar la autorización. La respuesta de autenticación de Credential Manager incluye información del usuario, como la dirección de correo electrónico, y también establece la cuenta predeterminada de la app en la cuenta seleccionada. Si es necesario, puedes hacer un seguimiento de esta cuenta en tu app. Una solicitud de autorización posterior usa la cuenta como valor predeterminado y omite el paso de selección de la cuenta en el flujo de autorización. Para usar una cuenta diferente para la autorización, consulta Autorización desde una cuenta no predeterminada.

  • En tu solicitud de autorización, además de los permisos que desees (por ejemplo, Drive scope), solicita los permisos userinfo, profile y openid. Después de que se devuelve un token de acceso, obtén la información del usuario realizando una solicitud GET HTTP al extremo de OAuth userinfo (https://www.googleapis.com/oauth2/v3/userinfo) con tu biblioteca HTTP preferida y, luego, incluye el token de acceso que recibiste en el encabezado, lo que equivale al siguiente comando curl:

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

    La respuesta es el UserInfo, limitado a los alcances que se solicitaron y con formato JSON.

Autorización desde una cuenta no predeterminada

Si usas Credential Manager para la autenticación y ejecutas AuthorizationClient.authorize(), la cuenta predeterminada de tu app se establecerá en la que seleccionó el usuario. Esto significa que cualquier llamada posterior para la autorización usará esta cuenta predeterminada. Para forzar la visualización del selector de cuentas, haz que el usuario salga de la app con la API de clearCredentialState() de Credential Manager.

Mantener el acceso continuo a los datos del usuario

Si necesitas acceder a los datos del usuario desde tu app, llama a AuthorizationClient.authorize() una vez. En las sesiones posteriores y siempre que el usuario no quite los permisos otorgados, llama al mismo método para obtener un token de acceso y lograr tus objetivos sin interacción del usuario. Por otro lado, si necesitas acceder a los datos del usuario en modo sin conexión desde tu servidor de backend, debes solicitar un tipo diferente de token llamado "token de actualización".

Los tokens de acceso se diseñaron intencionalmente para que sean de corta duración y tengan una vida útil de una hora. Si se intercepta o se vulnera un token de acceso, su período de validez limitado minimiza el posible uso inadecuado. Después de su vencimiento, el token deja de ser válido y el servidor de recursos rechazará cualquier intento de usarlo. Dado que los tokens de acceso son de corta duración, los servidores usan tokens de actualización para mantener el acceso continuo a los datos de un usuario. Los tokens de actualización son tokens con una vida útil prolongada que un cliente usa para solicitar un token de acceso de corta duración del servidor de autorización cuando el token de acceso anterior venció, sin ninguna interacción del usuario.

Para obtener un token de actualización, primero debes obtener un código de autorización durante el paso de autorización en tu app solicitando "acceso sin conexión" y, luego, intercambiar el código de autorización por un token de actualización en tu servidor. Es fundamental almacenar los tokens de actualización de larga duración de forma segura en tu servidor, ya que se pueden usar repetidamente para obtener tokens de acceso nuevos. Por lo tanto, se desaconseja enfáticamente almacenar tokens de actualización en el dispositivo debido a problemas de seguridad. En cambio, se deben almacenar en los servidores de backend de la app, donde se realiza el intercambio por un token de acceso.

Después de que se envíe el código de autorización al servidor de backend de tu app, puedes intercambiarlo por un token de acceso de corta duración en el servidor y un token de actualización de larga duración. Para ello, sigue los pasos que se indican en la guía de autorización de la cuenta. Este intercambio solo debe ocurrir en el backend de tu app.

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"));

En el siguiente fragmento, se supone que la autorización se inicia desde un fragmento.

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