Autoriza el acceso a los datos del usuario de Google

La autenticación establece la identidad de una persona y, por lo general, se conoce como registro o acceso del usuario. La autorización es el proceso de otorgar o rechazar el acceso a datos o recursos. Por ejemplo, tu app solicita el consentimiento de un 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 la API de Google, pero no son necesarias como parte de las funciones principales de la app, diséñala para que pueda controlar correctamente los casos en los que no se puede acceder a los datos de la API. Por ejemplo, puedes ocultar una lista de archivos guardados recientemente cuando el usuario no otorgó acceso a Drive.

Debes solicitar acceso a los permisos que necesitas para acceder a las APIs de Google solo cuando el usuario realiza una acción que requiere acceso a una API en particular. Por ejemplo, debes solicitar permiso para acceder a Drive del usuario cada vez que presione un 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 de consola de Google Cloud

  1. Abre tu proyecto en el Cloud Console, o crea uno si aún no tienes uno.
  2. En la página Branding, asegúrate de que toda la información esté completa y sea precisa.
    1. Asegúrate de que tu app tenga asignados un nombre, un logotipo y una página principal correctos. Estos valores se mostrarán a los usuarios en la pantalla de consentimiento de Acceder con Google durante el registro y en la pantalla 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 la página Clients, 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 la página Clients.
    2. Haz clic en Crear cliente.
    3. Selecciona el tipo de aplicación Android.
    4. Ingresa un nombre para el cliente de OAuth. Este nombre se muestra en la página Clients de tu proyecto para identificar al cliente.
    5. Ingresa el nombre del paquete de tu app para Android. Este valor se define en el package atributo del <manifest> elemento en tu AndroidManifest.xml archivo.
    6. Ingresa la huella digital del certificado de firma SHA-1 de la distribución de la app.
    7. Si tu app usa la firma de apps de Google Play, copia la huella digital SHA-1 de la página de firma de apps de Play Console.
    8. Si administras tu propio almacén de claves y claves de firma, usa la keytool utilidad incluida con Java para imprimir información del certificado en un formato legible. Copia el valor SHA-1 en la Certificate fingerprints sección del resultado de `keytool`. Consulta Cómo autenticar tu cliente en la documentación de las APIs de Google para Android para obtener más información.
    9. (Opcional) Verifica la propiedad de tu aplicación para Android.
  4. En la página Clients, crea un nuevo ID de cliente de "Aplicación web" si aún no lo hiciste. Por ahora, puedes ignorar los campos "Orígenes de JavaScript autorizados" y "URIs 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 la página Clients.
    2. Haz clic en Crear cliente.
    3. Selecciona el tipo Aplicación web.

Verifica la propiedad de la app

Puedes verificar la propiedad de tu aplicación para reducir el riesgo de suplantación de identidad.

Para completar el proceso de verificación, puedes usar tu cuenta de desarrollador de Google Play si tienes una y tu app está registrada en la Google Play Console. Se deben cumplir los siguientes requisitos para que la verificación se realice correctamente:

  • Debes tener una aplicación registrada en Google Play Console con el mismo nombre de paquete y la misma huella digital del certificado de firma SHA-1 que el cliente de OAuth de Android para el que estás completando la verificación.
  • Debes tener permiso de administrador para la app en Google Play Console. Obtén más información sobre la administración de acceso en Google Play Console.

En la sección Verificar propiedad de la app del cliente de Android, haz clic en el botón Verificar propiedad para completar el proceso de verificación.

Si la verificación se realiza correctamente, se mostrará una notificación para confirmar el éxito del proceso de verificación. De lo contrario, se mostrará un mensaje de error.

Para corregir una verificación fallida, prueba lo siguiente:

  • Asegúrate de que la app que estás verificando sea una app registrada en Google Play Console.
  • Asegúrate de tener permiso de administrador para la app en Google Play Console.

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.5.1"
}

Solicita los permisos que requieren las acciones del usuario

Cada vez que un usuario realice una acción que requiera un permiso adicional, llama a AuthorizationClient.authorize(). Por ejemplo, si un usuario realiza una acción que requiere acceso al almacenamiento de su 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.launch(IntentSenderRequest.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 requeridos 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 (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 {
            // 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 del lado del servidor, llama al getServerAuthCode() método desde AuthorizationResult para obtener un código de autorización que envías a tu backend para intercambiarlo por un token de acceso y un token de actualización. Para obtener más información, consulta Cómo mantener el acceso continuo a los datos del usuario.

Revoca los permisos para los datos o recursos del usuario

Para revocar el acceso otorgado anteriormente, llama a AuthorizationClient.revokeAccess(). Por ejemplo, si el usuario quita su cuenta de tu app y tu app recibió acceso a DriveScopes.DRIVE_FILE anteriormente, 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 vaya al servidor de OAuth. En el siguiente fragmento, se quita 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 permisos solicitados. Por ejemplo, la respuesta para obtener un token de acceso para acceder 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 su unidad. 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 usando 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 predeterminada y omite el paso de selección de 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 deseas (por ejemplo, el Drive scope), solicita los permisos userinfo, profile y openid. Después de que se muestre un token de acceso, obtén la información del usuario haciendo una solicitud HTTP GET al extremo de userinfo de OAuth (https://www.googleapis.com/oauth2/v3/userinfo) con tu biblioteca HTTP preferida y, luego, incluye el token de acceso que recibiste en el encabezado, que es equivalente al siguiente comando curl:

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

    La respuesta es UserInfo, limitada a los permisos que se solicitaron, con formato JSON.

Autorización desde una cuenta no predeterminada

Si usas Credential Manager para autenticar y ejecutar AuthorizationClient.authorize(), la cuenta predeterminada de tu app se establece en la que seleccionó el usuario. Esto significa que todas las llamadas posteriores para la autorización usan 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.

Cómo 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 para lograr tus objetivos, sin ninguna 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 de token diferente llamado "token de actualización".

Los tokens de acceso se diseñan intencionalmente para que sean de corta duración y tengan una vida útil de una hora. Si se intercepta o vulnera un token de acceso, su ventana de validez limitada 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 usa un cliente para solicitar un token de acceso de corta duración del servidor de autorización, cuando vence el token de acceso anterior, sin ninguna interacción del usuario.

Para obtener un token de actualización, primero debes obtener un código de Auth (o 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 Auth por un token de actualización en tu servidor. Es fundamental almacenar de forma segura los tokens de actualización de larga duración en tu servidor, ya que se pueden usar repetidamente para obtener tokens de acceso nuevos. Por lo tanto, no se recomienda 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 enviar 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 siguiendo los pasos de 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.launch(IntentSenderRequest.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
                }
            });
}