Hola a tod@s,
En este primer artículo de mi blog y de la categoría de artículos «consumo de servicios RESTful desde Android» vamos a ver como podemos verificar un token de Firebase enviado desde una app Android en un webservice RESTful programado en lenguaje PHP.
¿Para qué sirve esto?
Si tu app de Firebase se comunica con un servidor de backend personalizado, podría ser necesario que se identifique al usuario con sesión activa actualmente en el servidor. Según la documentación oficial de Firebase la manera segura de hacerlo es enviando el token de ID a tu servidor usando HTTPS después de que el usuario inicie sesión con éxito. Luego, en el servidor, se verifica la integridad del token y se pueden recuperar los datos contenidos por el mismo, por ejemplo para utilizar el ID único de usuario del token para identificar de manera segura en el backend al usuario con sesión iniciada.
Para verificar un token de ID con el Firebase Server SDK, Firebase nos proporciona de manera oficial para JAVA y NODE.js el método verifyIdToken
. Con este método si el token de ID no caducó y está firmado de manera adecuada, será decodificado.
Hasta aquí todo esto lo podemos ver muy bien documentado en el siguiente enlace.
¿Cuál es el problema?
El problema que yo me encontré queriendo implementar esta seguridad en una de mis apps, fue que el webservice con el que yo estoy conectando la app en cuestión está programado en lenguaje PHP y como podemos observar no hay ningún método nativo en Firebase por el momento para verificar tokens en este lenguaje.
¿Solución?
El problema que yo me encontré queriendo implementar esta seguridad en una de mis apps, fue que el webservice con el que yo estoy conectando la app en cuestión está programado en lenguaje PHP y como podemos observar no hay ningún método nativo en Firebase por el momento para verificar tokens en este lenguaje.
Sin embargo, buscando en la documentación de Firebase en inglés encontré información sobre cómo verificar tokens ID de Firebase en caso de que el lenguaje de tu webservice no esté soportado oficialmente por el Firebase server SDK y la forma de hacerlo es utilizar la librería de terceros JWT library (esta información no está disponible en la página de Firebase traducida al español).
Implementación
Diagrama de verificación de token ID Firebase en webservice para aumentar seguridad en las operaciones sobre la BD.
1.Autentificar usuario en Firebase Server.
Como podemos observar en el diagrama, la primera operación que tendríamos que realizar es inciar sesión correctamente en Firebase Server desde nuestra app Android para poder recoger el token ID del usuario actual de Firebase desde esta y enviarlo posteriormente al webservice para su verificación.
Para realizar este primer paso deberíamos tener implementado un sistema de login con Firebase Authentication SDK en nuestra app. El objeto de este artículo no es explicar como implementar un login con Firebase Authentication en Android, pero si tenéis dudas sobre como hacerlo os recomiendo que echéis un vistazo a la documentación oficial, o a este magnífico artículo en español publicado en Hermosa Programación por James Revelo.
2.Recoger token ID de Firebase en la app y enviarlo al webservice para su verificación.
Una vez el usuario a iniciado sesión con éxito en Firebase Server podemos proceder a recoger el token ID desde nuestra app para enviarlo al webservice mediante una llamada HTTP para su verificación.
A continuación se muestra un fragmento del código que podríamos utilizar para realizar esta tarea. En primer lugar se recoge el usuario actual con los métodos getInstance()
y getCurrentUser()
del Firebase SDK y posteriormente se recoge el token ID del usuario que nos proporciona Firebase, añadiendo un listener al método getToken()
que queda en escucha para realizar la llamada HTTP al webservice enviando el token ID recogido una vez finalizada la tarea.
//Se recoge el usuario actual FirebaseUser mUser = FirebaseAuth.getInstance().getCurrentUser(); assert mUser != null; //Se recoge el token ID que proporciona Firebase para el usuario actual*/ mUser.getToken(true) .addOnCompleteListener(new OnCompleteListener<GetTokenResult>() { public void onComplete(@NonNull Task<GetTokenResult> task) { if (task.isSuccessful()) { //Podemos recoger el token por el log para veririfcarlo en https://jwt.io/ Log.i(TAG, task.getResult().getToken()); /*Al finalizar la tarea de recogida del token, este se puede utilizar para pasarlo como parámetro en la llamada al webservice. En este ejemplo vamos a poner que se trata por ejemplo de una llamada al método POST del protcolo HTTP para insertar un nuevo registro en la BD*/ enviaRegistro(task.getResult().getToken(), payload); } } });
Nota: Es recomendable que se verifique manualmente el token obtenido en la siguiente web antes de continuar.
Para ilustrar un poco más este ejemplo vamos a suponer que la llamada HTTP de este caso sería una llamada al método POST que se realizaría utilizando la librería Retofit y que tendría el siguiente formato:
public interface RetrofitProvider { /*Método para enviar registros al webservices para su insercción en la BD*/ @POST("/api.tuapi.com/v1/registros/{token}") /*Se usa Rx Java para gestionar la respuesta y el objeto retornado será de tipo Single.*/ Single<Response<ResponseBody>> enviaRegistro(@Path("token") String token, @Body JsonObject payload); }
3.Verificación de token ID en servicio RESTful programado con PHP.
Una vez el usuario a iniciado sesión con éxito en Firebase Server podemos proceder a recoger el token ID desde nuestra app para enviarlo al webservice mediante una llamada HTTP para su verificación.
A continuación se muestra un fragmento del código que podríamos utilizar para realizar esta tarea. En primer lugar se recoge el usuario actual con los métodos getInstance()
y getCurrentUser()
del Firebase SDK y posteriormente se recoge el token ID del usuario que nos proporciona Firebase, añadiendo un listener al método getToken()
que queda en escucha para realizar la llamada HTTP al webservice enviando el token ID recogido una vez finalizada la tarea.
Si la petición POST realizada desde el cliente Android a través del método enviaRegistro
se ha culminado con éxito, ahora sería la función del webservice decodificar el token ID recibido y realizar las verificaciones oportunas para autorizar o no la inserción del nuevo registro en la BD.
En primer lugar tendríamos que preparar nuestro webservice PHP para poder decodificar el token, añadiendo la librería recomendada por Firebase JWT library.
Como se indica en las instrucciones de la librería podemos añadirla al proyecto utilizando Composer.
Con Composer instalado en nuestro equipo nos situamos en la ruta del proyecto del webservice y añadimos la dependencia de la librería JWT library desde la línea de comandos.
composer require firebase/php-jwt
Con esto, nuestro webservice ya debería estar configurado para poder decodificar y verificar el token enviado desde la app Android.
Procedemos a recibir el token y el payload enviados desde el cliente Android en la función post de la clase Registros.php
de nuestro servicio RESTful. Es necesario que la clase Registros.php
herede de la clase JWT.php
de la librería.
require_once(__DIR__."/../../vendor/firebase/php-jwt/src/JWT.php");
Se realiza una llamada al método verifyToken
de la clase registros.php
y si este verifica la integridad del token se procede a la inserción del nuevo registro en la BD y se devuelve un código de éxito, de lo contrario se devolverá un código 401 de no autorizado.
public static function post($token){ /* En el método post antes de realizar ninguna acción se verifica el token. Si es correcto se recoge el payload y se inserta un nuevo registro en la BD, de lo contario se devuelve un estado 401 */ if (registros::verifyToken($token[0])){ // Se recoge payload $body = file_get_contents('php://input'); $registro = json_decode($body,true); /* Se pasa el payload decodificado al método para insertar registros en BD */ registros::crear($registro); // Devuelve código éxito http_response_code(201); return [ "estado" => self::ESTADO_EXITO, "mensaje" => "Registro insertado", ]; }else{ http_response_code(401); return [ "estado" => self::ESTADO_NO_AUTORIZADO, "mensaje" => "No está autorizado para realizar esta acción", ]; } }
El método verifytoken
es el encargado de decodificar y verificar el token recibido mediante el siguiente proceso:
- Primero se recogen las claves públicas proporcionadas por Google en esta url. Según la documentación oficial de Firebase todos los tokens ID generados por Firebase se deberían poder decodificar utilizando alguna de las keys que se proporcionan en esa dirección.
- Se recoge el objeto Json que devuelve la llamada a la url, se parsea y se guardan todas las keys proporcionadas en un array (siempre hay solamente 3 o 4 keys).
- Se va probando a decodificar el token con cada una de las claves recogidas, hasta que se encuentra la correcta. El algoritmo criptográfico utilizado por Firebase es el «RS256» por lo que lo utilizamos para decodificar el token JWT.
- Una vez decodificado el token podemos acceder a sus atributos. En el caso del ejemplo casteamos el token JWT decodificado como array y accedemos a sus atributos a través del nombre de los claims. Según la documentación oficial estos son los JWT claims que podríamos utilizar para realizar la verificación del usuario en nuestro servidor.
En este código podemos ver el método verifytoken
y como para la verificación del token comprobamos que el valor de «aud» corresponde con el id de nuestro proyecto en Firebase. Una vez realizada esta comprobación con éxito se verifica si el «uid» del token existe también dentro de nuestra nuestra BD.
private function verifyToken($token){ // Se recogen la claves públicas proporcionadas por Google $url="https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"; $result = file_get_contents($url); $keys = json_decode($result, true); $keys_names = (array_keys($keys)); $longitud = count($keys); // Se recorren todas las claves probando cual es la que sirve para decodificar el token actual for($i=0; $i<$longitud; $i++){ try{ $decoded = JWT::decode($token, $keys[$keys_names[$i]], array("RS256")); /* Si se consigue decodificar el token con alguna de las claves se comprueba si contiene uid y si el valor de aud corresponde con el nombre de nuestra app Firebase. De ser así se verifica que el uid esta registrado también en nuestra BD */ $decoded_array = (array) $decoded; if (!empty($decoded_array["sub"]) && $decoded_array["aud"]=="miproyecto"){ if (registros::checkUIDenBD($decoded_array["sub"])){ return true; }else{ return false; } break; }else{ return false; } }catch (Exception $e){ return false; } } }
En el método checkUIDenBD
se utilizaría una sentencia como esta para verificar la existencia del «uid» de Firebase en nuestra BD.
("SELECT userid FROM usuarios WHERE userid ='".$uidfirebase."'");
Esto implica que para que la verificación se realice con éxito se debería haber guardado el «uid» de Firebase en nuestra BD cuando se dio de alta al usuario en este servicio.
Conclusión
En mi opinión utilizar el Firebase Authentication SDK como parte de la seguridad de nuestras apps es una muy buena opción para ahorrar tiempo y muchos de los quebraderos de cabeza que supone crear un sistema de login propio en nuestro servidor. Además de tener otras ventajas como admitir la autenticación con proveedores como Google, Facebook y Twitter, etc., o poder aprovechar su potencial para integrar con sencillez otras funcionalidades del SDK de Firebase como las Notificaciones Push.
En este artículo hemos visto como podemos aumentar la seguridad con Firebase no limitándose solo la identificación del usuario en Firebase Server desde la app Android, si no extendiéndola también a las tareas que realizamos a través de nuestro webservice.
Si tienes un webservice RESTful programado en PHP y te limitaba la falta de soporte oficial de Firebase para la verificación de tokens en ese lenguaje, ya has visto como puedes llevarla a cabo utilizando la librería JWT.
Espero que este artículo os haya resultado interesante y que no seáis demasiado duros conmigo pues es mi primer artículo en el blog 😉
Quedo abierto a cualquier tipo de sugerencia, observación o propuesta. Tenéis disponibles los comentarios y las redes sociales para comunicaros conmigo.
Si os parece útil este tipo de artículos no dudéis en suscribiros al blog para que os lleguen las novedades por correo electrónico.
En las próximas semanas tengo pensado seguir compartiendo con vosotros artículos que tratarán sobre consumo de APIs RESTful y diseño UI en Android.
¡Un saludo a tod@s!
muy buena información, debido a que no se encuentra en el SDK, probare la integración y te cuento como me fue.
Saludos!