
Si desean 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. El usuario no solo debe estar autenticado para acceder al servicio, sino que la app también debe estar autorizada para actuar en nombre del usuario.
El protocolo OAuth2 es la manera estándar de la industria para lograr la autenticación de servicios de terceros. OAuth2 proporciona un valor único, denominado token de autenticación, que representa tanto la identidad del usuario como la autorización de la app para actuar en nombre de este. En esta lección, se muestra la conexión con 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 correctamente el protocolo OAuth2.
El uso de OAuth2 es útil para lo siguiente:
- Obtener el permiso del usuario para acceder a un servicio en línea utilizando su cuenta
- Lograr la autenticación para un servicio en línea en nombre del usuario
- Control de errores de autenticación
Cómo recopilar información
Para comenzar a usar OAuth2, debes saber algunas cuestiones sobre la API a la que estás intentando acceder:
- La URL del servicio al que deseas acceder.
- El alcance de autenticación, que es una string 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 ID y secretos de clientes. En el artículo Cómo autorizar y usar las API de REST, se explica cómo usar este sistema a fin de obtener estos valores para usarlos con la API de Google Tasks.
Cómo solicitar el permiso de Internet
Para las apps que se orientan a Android 6.0 (API nivel 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 de 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 involucrar comunicaciones 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, deberás implementarlo 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 se encuentra 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 funciona según lo esperado, el Bundle
contendrá un token válido en la clave KEY_AUTHTOKEN
y podrás continuar. Sin embargo, en ocasiones puede haber algún problema…
Cómo solicitar un token de autenticación… otra vez
Es posible que la primera solicitud de un token de autenticación falle debido a varias razones:
- 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 apps pueden manejar los dos primeros casos de manera simple, en general con solo mostrar un mensaje de error al usuario. Si la red no funciona o el usuario decidió no otorgar acceso, tu app no podrá hacer mucho al respecto. Los últimos dos casos son un poco más complicados, ya que se espera que las apps con buen comportamiento manejen automáticamente esas fallas.
El tercer caso de falla, por credenciales insuficientes, se comunica mediante Bundle
que recibes en tu AccountManagerCallback
(OnTokenAcquired
del ejemplo anterior). Si Bundle
incluye una Intent
en la clave KEY_INTENT
, el autenticador te indica que debes interactuar directamente con el usuario para que te pueda dar 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. Tal vez la cuenta del usuario haya caducado y deba acceder nuevamente, o quizás las 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 del Intent
mediante la implementación de onActivityResult()
en tu propia actividad. Este paso es muy importante. Si no capturas el resultado del Intent
de la respuesta del autenticador, será imposible saber si el usuario se autenticó correctamente o no.
Si el resultado es RESULT_OK
, 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()
otra vez para solicitar el nuevo token de autenticación.
El último caso, en el que caducó el token, en realidad, no es un error de AccountManager
. La única manera de descubrir si un token caducó o no 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.
Este error solo se puede detectar cuando una app 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. Debido a que Google usa el protocolo OAuth2 estándar de la industria para autenticar las solicitudes, las técnicas discutidas aquí son ampliamente aplicables. Sin embargo, debes tener en cuenta que cada servidor es diferente. Es posible que debas hacer pequeños ajustes en estas instrucciones para justificar tu situación específica.
Para las API de Google, debes proporcionar cuatro valores con cada solicitud: la clave de API, el ID de cliente, el secreto de cliente y la clave de autenticación. Los tres primeros se toman del sitio web de la Consola de API de Google. El último es el valor de string que obtuviste mediante un llamado 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 HTTP 401, significa que se rechazó el token. Como se mencionó en la última sección, el motivo más común de esto es que el token caducó. La solución es simple: llama a AccountManager.invalidateAuthToken()
y repite los pasos para la adquisición del token.
Debido a que los tokens caducados son muy comunes y arreglarlos es tan fácil, muchas apps suponen que el token caducó aún antes de pedirlo. Si la renovación de un token es una operación económica para tu servidor, tal vez prefieras llamar a AccountManager.invalidateAuthToken()
antes de la primera llamada a AccountManager.getAuthToken()
, con lo que no deberías solicitar un token de autenticación dos veces.