Para acceder de forma segura a un servicio en línea, los usuarios deben autenticarse; es decir, deben proporcionar pruebas de su identidad. Para una aplicación que accede a un servicio de terceros, el problema de seguridad es aún más complicado. No solo el usuario debe estar autenticado para acceder al servicio, sino que la aplicación también debe estar autorizada para actuar en nombre del usuario.
El protocolo OAuth2 es el método estándar de la industria para abordar la autenticación en servicios de terceros. OAuth2 proporciona un valor único, denominado token de autenticación, que representa la identidad del usuario y la autorización de la aplicación para actuar en nombre del usuario. En esta lección, se muestra cómo conectarse a un servidor de Google compatible con OAuth2. Aunque los servicios de Google se usan como ejemplo, las técnicas demostradas funcionarán en cualquier servicio que admita el protocolo OAuth2 de forma correcta.
El uso de OAuth2 es útil para lo siguiente:
- Obtener permiso del usuario para acceder a un servicio en línea con su cuenta
- Lograr la autenticación para un servicio en línea en nombre del usuario
- Control de errores de autenticación
Recopila información
Para comenzar a usar OAuth2, debes conocer algunos aspectos específicos de la API sobre el servicio al que intentas acceder:
- La URL del servicio al que deseas acceder.
- El alcance de Auth, que es una cadena que define el tipo específico de acceso que solicita tu app. Por ejemplo, el alcance de autenticación para el acceso de solo lectura a Google Tasks es
View your tasks
, mientras que el alcance de autenticación para el acceso de lectura y escritura a Google Tasks esManage your tasks
. - Un ID de cliente y un secreto de cliente, que son strings que permiten que el servicio identifique tu app. Debes obtener estas strings directamente del propietario del servicio. Google tiene un sistema de autoservicio para obtener IDs de cliente y secretos.
Cómo solicitar el permiso de Internet
En el caso de las apps orientadas a Android 6.0 (nivel de API 23) y versiones posteriores, el método getAuthToken()
no requiere ningún permiso. Sin embargo, para realizar operaciones en el token, debes agregar el permiso INTERNET
al archivo de manifiesto, como se muestra en el siguiente fragmento de código:
<manifest ... > <uses-permission android:name="android.permission.INTERNET" /> ... </manifest>
Cómo solicitar un token de autenticación
Para obtener el token, llama a AccountManager.getAuthToken()
.
Precaución: Debido a que algunas operaciones de la cuenta pueden implicar comunicación de red, la mayoría de los métodos AccountManager
son asíncronos. Esto significa que, en lugar de hacer todo el trabajo de autenticación en una función, debes implementarla como una serie de devoluciones de llamada.
En el siguiente fragmento, se muestra cómo trabajar con una serie de devoluciones de llamada para obtener el 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
En este ejemplo, OnTokenAcquired
es una clase que implementa AccountManagerCallback
. AccountManager
llama a run()
en OnTokenAcquired
con un AccountManagerFuture
que contiene un Bundle
. Si la llamada se realizó correctamente, el token estará dentro de Bundle
.
A continuación, te indicamos cómo puedes obtener el token 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 todo resulta bien, Bundle
contiene un token válido en la clave KEY_AUTHTOKEN
y está todo listo.
Es posible que tu primera solicitud de un token de autenticación falle por varios motivos:
- Se produjo un error en el dispositivo o la red hizo que
AccountManager
fallara. - El usuario decidió no permitir que tu app accediera a la cuenta.
- Las credenciales almacenadas de la cuenta no son suficientes para obtener acceso a la cuenta.
- El token de autenticación en caché caducó.
Las aplicaciones pueden manejar los dos primeros casos de manera trivial, en general con solo mostrar un mensaje de error al usuario. Si la red no funciona o el usuario decidió no otorgar acceso, tu aplicación no puede hacer mucho al respecto. Los últimos dos casos son un poco más complicados, ya que se espera que las aplicaciones con buen comportamiento manejen automáticamente estas fallas.
El tercer caso de error, por tener credenciales insuficientes, se comunica a través del Bundle
que recibes en tu AccountManagerCallback
(OnTokenAcquired
del ejemplo anterior). Si el Bundle
incluye un Intent
en la clave KEY_INTENT
, el autenticador te indica que debes interactuar directamente con el usuario para que pueda darte un token válido.
Puede haber muchos motivos para que el autenticador muestre un Intent
. Puede ser la primera vez que el usuario accede a esta cuenta. Es posible que la cuenta del usuario haya vencido y deba acceder de nuevo, o tal vez sus credenciales almacenadas sean incorrectas. Es posible que la cuenta requiera autenticación de dos factores o que se deba activar la cámara para realizar un escaneo de retina. No importa cuál sea el motivo. Si quieres un token válido, tendrás que activar el Intent
para obtenerlo.
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; } } }
Ten en cuenta que, en el ejemplo, se usa startActivityForResult()
de modo que puedas capturar el resultado de Intent
mediante la implementación de onActivityResult()
en tu propia actividad. Esto es importante: si no capturas el resultado del Intent
de la respuesta del autenticador, es imposible saber si el usuario se autenticó correctamente.
Si el resultado es RESULT_OK
, significa que el autenticador actualizó las credenciales almacenadas a fin de que sean suficientes para el nivel de acceso que solicitaste, y debes llamar a AccountManager.getAuthToken()
nuevamente para solicitar el token de autenticación nuevo.
El último caso, en el que caducó el token, en realidad no es una falla de AccountManager
. La única forma de descubrir si un token está vencido es comunicarse con el servidor, y para AccountManager
sería costoso y una pérdida de tiempo conectarse continuamente a fin de verificar el estado de todos sus tokens. Por lo tanto, este es un error que solo se puede detectar cuando una aplicación como la tuya intenta usar el token de autenticación para acceder a un servicio en línea.
Cómo conectarse con el servicio en línea
En el siguiente ejemplo, se muestra cómo realizar la conexión con un servidor de Google. Dado que Google usa el protocolo OAuth2 estándar de la industria para autenticar las solicitudes, las técnicas que se analizan aquí se pueden aplicar ampliamente. Sin embargo, ten en cuenta que cada servidor es diferente. Es posible que necesites hacer pequeños ajustes en estas instrucciones para justificar tu situación específica.
Las APIs de Google requieren que proporciones cuatro valores con cada solicitud: la clave de API, el ID de cliente, el secreto de cliente y la clave de autenticación. Las tres primeras provienen del sitio web
de la Consola de API de Google. El último es el valor de string que obtuviste cuando llamas a AccountManager.getAuthToken()
. Debes pasarlos al servidor de Google como parte de una solicitud 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 solicitud muestra un código de error de HTTP 401, significa que se rechazó el token. Como se mencionó en la
última sección, el motivo más común
es que el token venció. La solución es simple: llama a AccountManager.invalidateAuthToken()
y repite el proceso de adquisición de tokens una vez más.
Debido a que los tokens vencidos son muy comunes y solucionarlos es muy sencillo, muchas aplicaciones suponen que el token expiró incluso antes de solicitarlo. Si renovar un token es una
operación económica para tu servidor, te recomendamos llamar a
AccountManager.invalidateAuthToken()
antes de la primera llamada a
AccountManager.getAuthToken()
y no tener que solicitar un token de
autenticación dos veces.