.. role:: problema-contador Servicios web ============= Los *servicios web* proporcionan una forma estándar de interoperabilidad entre diferentes aplicaciones, posiblemente heterogéneas. En este tema, veremos cómo crear y cómo acceder a estos servicios, así como su relación con el estándar HTTP. Además, abordaremos el estudio de la arquitectura de servicios conocida como REST (por el inglés, *representation state transfer*), en la que los agentes proporcionan una interfaz con una semántica uniforme (que, básicamente, se corresponde con las operaciones para crear, recuperar, actualizar y borrar recursos), en lugar de interfaces arbitrarias o específicas de una aplicación concreta. En un primer momento, nos centraremos en el acceso a servicios web ofrecidos por terceros; posteriormente, veremos cómo definir nuestros propios servicios web. .. Important:: Las habilidades que deberías adquirir con este tema incluyen las siguientes: - Saber usar Ajax, especialmente a través de la API Fetch, como una tecnología para crear aplicaciones de una única página. - Saber crear y encadenar promesas en un programa de JavaScript. - Entender la *política del mismo origen* y cómo superarla con la técnica CORS. - Entender los fundamentos de la arquitectura REST. - Comprender los fundamentos del protocolo HTTP y la configuración cliente-servidor. - Diferenciar los distintos componentes de un navegador. - Saber implementar aplicaciones *mashups* que accedan e integren servicios web de terceros. El protocolo HTTP ----------------- HTTP (por *hypertext transfer protocol*) es el protocolo de comunicación (a nivel de capa de aplicación) diseñado para permitir el envío de información en la *world wide web* entre distintos ordenadores. Sus primeras versiones fueron desarrolladas en la década de los noventa a partir de los trabajos de Tim Berners Lee y su equipo (los creadores de la web). Las diferentes versiones del estándar son coordinadas por el `HTTP Working Group`_ de la Internet Engineering Task Force. HTTP/1.1 se publicó en 1997, HTTP/2 en 2015 y HTTP/3 en 2018, aunque tanto navegadores como servidores siguen soportando las versiones antiguas y son capaces de adaptarse para usar el mismo protocolo: por ejemplo, un navegador reciente que soporte HTTP/3 usará HTTP/2 o incluso HTTP/1.1 al comunicarse con un servidor que no haya sido actualizado en mucho tiempo. HTTP/3, a diferencia de anteriores versiones, ya no se apoya en el protocolo de transporte TCP (*transmission control protocol*), sino que usa QUIC (que no son siglas, sino un término que pretende evocar a la palabra inglesa *quick*). QUIC mejora la capacidad de multiplexación de TCP y suele mejorar la latencia y velocidad de las conexiones. .. _`HTTP Working Group`: https://httpwg.org/ Los conceptos que tienes que comprender del protocolo HTTP se encuentran recogidos en `estas diapositivas`_. .. _`estas diapositivas`: _static/slides/050-http-slides.html Herramientas para desarrolladores --------------------------------- Las herramientas para desarrolladores que incorporan los navegadores permiten estudiar los detalles de todas las conexiones establecidas por el navegador al ejecutar una aplicación web. .. Note: Las peticiones lanzadas desde la barra de direcciones del navegador son siempre de tipo GET. Para estudiar los otros tipos de peticiones de HTTP, necesitamos crear un formulario para peticiones de tipo POST, o, si queremos poder usar cualquier verbo, escribir código en JavaScript (usando la API Fetch que estudiáremos después) u otro lenguaje de programación. También hay herramientas como `Postman`_, una aplicación que permite crear cómodamente colecciones de peticiones a APIs (*application programming interfaces*). La herramienta ``curl`` es similar pero funciona desde la línea de órdenes. .. _`Postman`: https://www.getpostman.com/ .. admonition:: Hazlo tú ahora :class: hazlotu Familiarízate con las opciones de inspección de la actividad en red de la pestaña :guilabel:`Network` del entorno de las Chrome DevTools siguiendo la página de su documentación `Inspect Network Activity`_ . Practica las distintas posibilidades en DevTools con peticiones GET por ahora, pero recuerda volver a esta actividad cuando estudies cómo realizar desde JavaScript peticiones con otros verbos. .. _`Inspect Network Activity`: https://developers.google.com/web/tools/chrome-devtools/network Promesas de JavaScript ---------------------- Los objetos de tipo ``Promise`` (una clase definida en la API de JavaScript de los navegadores modernos) se usan para representar la finalización con éxito de una tarea (normalmente asíncrona, es decir, que no bloquea el bucle de eventos, sino que define una función de *callback* que será ejecutada más adelante) o su fracaso. En términos informales, diremos que una promesa se cumple (en caso de éxito), se incumple (en caso de fracaso) o que está pendiente (cuando aún no se ha resuelto); en inglés se usan los términos *fullfilled*, *rejected* o *pending*, respectivamente. Los objetos promesa nos serán muy útiles en tareas como la comunicación con un servidor, que veremos más adelante en este tema. .. promesas: https://stackoverflow.com/a/42005046/1485627 Veamos un primer ejemplo. El siguiente código crea un objeto de tipo promesa ``p`` invocando al constructor de ``Promise``. Este constuctor recibe dos funciones de *callback* que son aportadas por el sistema (es decir, no son definidas por el programador); estas dos funciones (``resolve`` y ``reject`` en el código de abajo) serán invocadas en los lugares de nuestro código en los que determinemos que la tarea se ha desarrollado con éxito (en el caso del primer parámetro) o ha fracasado (función pasada como segundo parámetro). En este caso, vamos a suponer que nuestra promesa ejecuta una tarea asíncrona muy sencilla: esperar un segundo y generar un número aleatorio entre 0 y 1; la tarea se considera un éxito si el número es menor de 0,5. En lenguaje de la calle, sería como decir "te prometo que voy a generar (asíncronamente) un número aleatorio y que será menor que 0,5"; como cualquier promesa, en cualquier caso, esta puede cumpirse o no. .. code-block:: :linenos: let p= new Promise(function(resolve,reject) { console.log('Entrando en el constructor de la promesa'); setTimeout( function() { const r= Math.random(); if (r<0.5) { resolve('la cosa fue bien'); } else { reject('algo salió mal'); } }, 1000); }); p.then(function(mensaje) { console.log(`Promesa cumplida: ${mensaje}`); }, function(mensaje) { console.log(`Promesa incumplida: ${mensaje}`); } ); Las funciones ``resolve`` y ``reject`` reciben un único argumento que usaremos para dar información adicional relacionada con el éxito o fracaso de la tarea asíncrona; en este caso, es una simple cadena con un mensaje, pero puede ser un objeto que incluya un conjunto de atributos. Nada más invocar al contructor de ``Promise`` se establece el estado de la promesa a *pendiente* y se ejecuta el código de la función pasada como parámetro al constructor. Este código normalmente definirá una operación asíncrona (como realizar una petición a un servidor) y terminará llamando a ``resolve`` o ``reject``; estas funciones (recordemos que son creadas por el sistema y no pertenecen a nuestro código) cambian el estado de la promesa a *cumplida* o *incumplida* y llaman a las funciones que el programador haya definido para manejar ambas situaciones. El vínculo entre las funciones del sistema ``resolve`` y ``reject`` y nuestro código se establece llamando al método ``then`` sobre el objeto de tipo promesa. Al método ``then`` se le pasan dos funciones: la primera se vincula a ``resolve`` y la segunda a ``reject``. Estas funciones pueden tener un parámetro que será el mismo que el usado como argumento en las llamadas a ``resolve`` y ``reject`` del constructor de ``Promise``. .. Note: Realmente, las llamadas a ``resolve`` y ``reject`` también se realizan de forma asíncrona, de manera que para entonces el intérprete ya habrá ejecutado el método ``then`` correspondiente. Finalmente, observa en el código anterior cómo hemos usado *cadenas con plantillas* (*template strings*) para imprimir los mensajes por consola. Las cadenas con plantillas de JavaScript usan comillas invertidas en lugar de rectas, pueden contener variables embebidas como en el ejemplo, y pueden también ocupar más de una línea. .. figure:: https://csharpcorner-mindcrackerinc.netdna-ssl.com/UploadFile/BlogImages/01262017214716PM/Screen%20Shot%202017-01-26%20at%208.28.19%20pm.png :target: https://www.c-sharpcorner.com/blogs/overview-of-promises-in-javascript :alt: diagrama de los elementos implicados en una promesa Diagrama de los elementos de una promesa por Sumant Mishra El código anterior es equivalente al siguiente en el que en lugar de pasar dos funciones a ``then`` se define la función asociada al incumplimiento de la promesa en un método ``catch``: .. code-block:: :linenos:
let p= new Promise(function(resolve,reject) { setTimeout( function() { const r= Math.random(); if (r<0.5) { resolve('la cosa fue bien'); } else { reject('algo salió mal'); } }, 1000); }); p.then(function(mensaje) { console.log(`Promesa cumplida: ${mensaje}`); }) .catch(function(mensaje) { console.log(`Promesa incumplida: ${mensaje}`); }); Además, si encapsulamos el código de creación de la promesa en una función, usamos funciones flecha y gestionamos el error mediante una excepción (clase ``Error``), el código anterior se convierte en: .. code-block:: :linenos: function aleatorio() { return new Promise( (resolve,reject) => { setTimeout( () => { const r= Math.random(); if (r<0.5) { resolve('la cosa fue bien'); } else { reject(Error('algo salió mal')); } }, 1000); }); aleatorio() .then( (mensaje) => {console.log(`Promesa cumplida: ${mensaje}`);}) .catch( (mensaje) => {console.log(`Promesa incumplida: ${error.message}`);});
Las promesas pueden concatenarse simplemente haciendo que la función asociada al cumplimiento de la promesa (la indicada en la llamada al método ``then``) devuelva a su vez una promesa: .. code-block:: :linenos: aleatorio().then( (mensaje) => { console.log(`Primera promesa cumplida: ${mensaje}`); return aleatorio2(0.8); }) .then( (mensaje) => { console.log(`Segunda promesa cumplida: ${mensaje}`); }) .catch( (error) => { console.log(`Una de las promesas fue incumplida: ${error.message}`); } ); function aleatorio() { return new Promise( (resolve,reject) => { setTimeout( () => { const r= Math.random(); if (r<0.5) { resolve('la cosa fue bien'); } else { reject(Error('algo salió mal')); } }, 1000); }); } function aleatorio2(delta) { return new Promise( (resolve,reject) => { setTimeout( () => { const r= Math.random(); if (r { setTimeout( () => { const r= Math.random(); if (r<0.5) { resolve('la cosa fue bien'); } else { reject(Error('algo salió mal')); } }, 1000); }); } function aleatorio2(delta) { return new Promise( (resolve,reject) => { setTimeout( () => { const r= Math.random(); if (r { const r= Math.random(); if (r<0.5) { let mensaje= 'la cosa fue bien'; alert(`Primera promesa cumplida: ${mensaje}`); let delta= 0.8; setTimeout( () => { const r= Math.random(); if (r0) { for (var i=0; i> $HOME/.bashrc Abre un nuevo terminal para que el nuevo valor de la variable de entorno ``PATH`` se aplique. Ahora deberías poder ver la versión de Node.js instalada con:: node -v La aplicación del carrito usa en modo local el gestor de base de datos *ligero* `SQLite3`_ para no depender de gestores más complejos. Cuando la aplicación se despliegue en la nube (un poco más adelante lo haremos en Heroku y, posteriormente, en Google Cloud Platform), usará otros gestores de bases de datos, lo que explica las diferentes opciones dentro de la función ``conectaBD``. Comprueba si ya tienes SQLite instalado ejecutando ``sqlite3`` desde la línea de órdenes. Si no lo tienes, para el caso de Linux puedes descargar este fichero:: curl -O https://www.sqlite.org/2019/sqlite-tools-linux-x86-3300100.zip .. _`SQLite3`: https://www.sqlite.org/index.html Descomprime el fichero anterior en tu directorio raíz y añade el nuevo directorio a la variable ``PATH`` del sistema:: unzip -q sqlite-tools-linux-x86-3300100.zip -d $HOME echo 'export PATH=$HOME/sqlite-tools-linux-x86-3300100:$PATH' >> $HOME/.bashrc Abre un nuevo terminal para que el nuevo valor de la variable de entorno ``PATH`` se aplique. Los binarios de SQLite están compilados para 32 bits, por lo que es posible que necesites instalar algunas librerías adicionales de 32 bits, ya que tu sistema es proablemente de 64 bits; la siguiente orden es para Ubuntu:: sudo apt-get install libc6-i386 lib32z1 Ahora deberías poder ver la versión de SQLite3 instalada con:: sqlite3 -version A continuación, descarga el código del cliente y del servidor de la aplicación del carrito; clona para ello el `repositorio de la asignatura`_ haciendo:: git clone https://github.com/jaspock/dai1920.git Entra en el directorio ``code/carrito`` y ejecuta:: npm install node app.js La primera línea instala en la carpeta ``node_modules`` todas las dependencias indicadas en el fichero ``package.json``. La segunda línea lanza el motor de JavaScript sobre el fichero indicado. Como este fichero contiene una aplicación web escrita con el framework Express, este la ejecuta sobre un puerto local, por lo que podremos acceder a ella abriendo en el navegador una dirección como ``localhost:5000`` o similar. .. Note:: Si quisieras usar un nuevo paquete en tu aplicación (lo que probablemente no ocurrirá en esta asignatura), deberías ejecutar:: npm install paquete donde ``paquete`` es el nombre del nuevo módulo; esta orden instala el nuevo módulo en la carpeta ``node_modules`` y, además, añade la línea adecuada al fichero ``package.json``. .. Note:: Observa la sección ``scripts`` del fichero ``package.json``. Allí puedes definir diversas maneras de arrancar tu aplicación con diferentes configuraciones. En este caso, solo hay una entrada ``start`` que te permitiría lanzar también tu aplicación haciendo:: npm start Depuración y prueba de la aplicación ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Si deseas depurar el código del servidor en modo local puedes usar el editor de texto `Visual Studio Code`_. Abre con él el fichero ``app.js`` y selecciona :guilabel:`Debug / Start debugging`. Puedes definir puntos de ruptura haciendo click en el borde de la línea de código oportuna. .. _`Visual Studio Code`: https://code.visualstudio.com/ Suele ser útil también poder ver un informe detallado de qué rutas puede procesar Express y qué hace con cada petición que llega; para ello puedes lanzar el servidor en modo depuración en Linux con:: DEBUG=express:* node app.js Para depurar la aplicación cuando esta se encuentra desplegada en la nube, se necesitan algunas instrucciones adicionales. En el caso de nuestra aplicación, como el código que se ejecuta en ``localhost`` o en la nube es prácticamente el mismo, si la aplicación funciona en local, apenas deberían aparecer problemas en la nube. .. Note:: Ten en cuenta que el código de JavaScript que se ejecuta en el navegador se seguirá depurando desde las Chrome DevTools. Durante la depuración de la aplicación irás alternando entre el navegador y Visual Studio Code según se esté ejecutando el código del lado del cliente o del servidor, respectivamente. Al ejecutar la aplicación en modo local se usa el gestor de base de datos SQLite, que almacena la base de datos en un fichero indicado como opción de inicialización a Knex.js (en nuestra aplicación el fichero se indica en ``config.js``). Si quieres borrar toda la base de datos para empezar de cero, basta con que borres ese fichero, que será creado de nuevo la siguiente vez que Knex.js quiera acceder a él. Si quieres, realizar consultas a la base de datos local desde un cliente de SQL puedes hacer desde la línea de órdenes:: sqlite3 donde has de indicar como argumento el nombre del fichero de la base de datos (si no has editado los ficheros de configuración del carrito se llamará ``midb.sqlite``). Desde dentro del cliente puedes ejecutar instrucciones como:: .tables para ver las tablas de la base de datos o:: select * from productos; para consultarlas. Modificación de la API REST --------------------------- En esta actividad, vas a realizar una pequeña modificación a la API del carrito y a la aplicación web que la utiliza. El desarrollo lo realizarás en tu máquina y, cuando hayas comprobado que todo funciona correctamente, lo subirás en la siguiente actividad a un servidor de aplicaciones en la nube. .. Hint:: Si vas a desarrollar frecuentemente con Node.js, te vendrá bien utilizar la herramienta `nodemon`_, que evita que tengas que matar y volver a lanzar el servidor local cada vez que hagas un cambio en la aplicación. .. _`nodemon`: https://www.npmjs.com/package/nodemon .. admonition:: Hazlo tú ahora :class: hazlotu Modifica la parte del cliente y del servidor de la aplicación del carrito para que junto con la cantidad se pueda añadir el precio unitario de cada item. Necesitarás instalar en tu sistema Node.js y el gestor de base de datos SQLite3; sigue para ello los pasos detallados en la actividad ":ref:`label-local`". Salvo que uses ``nodemon``, como se ha comentado antes, tendrás que matar y relanzar el servidor para que se apliquen los cambios. Como siempre, tendrás que recargar la página en el navegador siempre que realices algún cambio en el código del cliente. .. _label-heroku: Despliegue de la aplicación web en Heroku ------------------------------------------ Cuando tengas la aplicación lista en modo local, puedes desplegarla en la plataforma en la nube de `Heroku`_ como sigue. Copia para empezar la carpeta ``dai1920/code/carrito`` en otra ubicación de tu sistema. Al copiar la carpeta a una ubicación diferente haces que su contenido no esté ligado al repositorio de Github, ya que para desplegar la aplicación en Heroku necesitas vincularla a otro repositorio. Instala el cliente de línea de órdenes (CLI, por *command-line interface*) de Heroku con las `instrucciones de esta página`_. En el caso de Linux basta con descargar el fichero con los binarios, descomprimirlo y añadir la carpeta ``bin`` a la variable ``PATH`` del sistema:: curl -O https://cli-assets.heroku.com/heroku-linux-x64.tar.gz tar xzf heroku-linux-x64.tar.gz -C $HOME echo 'export PATH=$HOME/heroku/bin:$PATH' >> $HOME/.bashrc Si tienes permisos de administrador puedes instalar de forma alternativa el cliente de línea de órdenes de Heroku en Ubuntu con:: sudo curl https://cli-assets.heroku.com/install.sh | sh o alternativamente:: sudo snap install --classic heroku Continúa ahora configurando tu proyecto haciendo:: git init git add . git commit -m "cambios" Con lo anterior, se crea un repositorio con los ficheros del proyecto, que podrás subir (*push*) a Heroku. Crea una cuenta en la web de Heroku e identifícate en el cliente de línea de órdenes ejecutando:: heroku login Crea un proyecto en la nube haciendo:: heroku create --region eu Desde este momento ya podrás `desplegar la aplicación`_ con:: git push heroku master .. Note:: Heroku puede, en principio, leer del fichero ``app.json`` datos como el gestor de base de datos a utilizar o el valor de ciertas variables de entorno que estarán definidas en el entorno de producción, pero en el momento de escribir esto no funciona. Por ello, has de configurar estos aspectos de tu aplicación ejecutando lo siguiente desde la línea de órdenes:: heroku addons:create heroku-postgresql:hobby-dev heroku config:set CARRITO_ENV=heroku Si ejecutas ``heroku config`` podrás ver que ahora hay dos variables de entorno: ``CARRITO_ENV`` y ``DATABASE_URL``. Finalmente, abre la aplicación en el navegador con:: heroku open Puedes estudiar los mensajes de actividad emitidos a la consola por tu aplicación implementada en Heroku con:: heroku logs Para verlos conforme se van produciendo:: heroku logs --tail .. _`Node Version Manager`: https://github.com/nvm-sh/nvm .. _`repositorio de la asignatura`: https://github.com/jaspock/dai1920 .. _`instrucciones de esta página`: https://devcenter.heroku.com/articles/heroku-cli#download-and-install .. _`desplegar la aplicación`: https://devcenter.heroku.com/articles/git .. _`Heroku`: https://www.heroku.com/ Si haces cambios en la aplicación, basta con repetir estos pasos para actualizar la aplicación en Heroku:: git add . git commit -m "cambios" git push heroku master Finalmente, puedes usar el `panel de control`_ de tu aplicación en Heroku para acceder a ciertas opciones adicionales de configuración. Por otro lado, en el panel de control de la `base de datos`_ PostgreSQL puedes visitar la sección :guilabel:`Dataclips` para poder ver las tablas de tu base de datos y lanzar consultas SQL sobre ellas. .. _`panel de control`: https://dashboard.heroku.com/ .. _`base de datos`: https://data.heroku.com/ .. para ligar un nuevo proyecto a una aplicación ya existente: heroku git:remote -a new-project-23116 .. _label-signin: Autenticación de usuarios ------------------------- Una componente importante de la mayoría de aplicaciones web es la que permite que los usuarios se identifiquen o autentiquen en la aplicación y puedan gestionar así sus propios datos. La gestión de cuentas de usuario requiere un esfuerzo adicional (validación de cuentas de correo electrónico, almacenamiento seguro de las contraseñas, gestión de ataques cibernéticos, olvidos de contraseña, etc.) que podemos delegar en servicios de terceros como Facebook Login o Google Sign-in; este último será el que usaremos en esta actividad. De esta forma, el usuario se identifica en una ventana del navegador vinculada a un URL de Google y autoriza a nuestra aplicación a acceder a cierta información de su perfil (nombre y correo electrónico, por ejemplo) sin compartir el resto de sus datos (o permitiendo el acceso a un subconjunto de ellos, como, por ejemplo, los ficheros almacenados en una carpeta de Google Drive). .. Note:: Esta actividad solo es necesaria este curso si vas a implementar la parte de la práctica 4 relacionada con la identificación de usuarios. Si no es así, puedes ignorar el resto, salvo que tengas curiosidad en aprender cómo se hace. El primer paso para que una aplicación pueda acceder al servicio de identificación de Google Sign-in es obtener las credenciales adecuadas que nos permitan obtener el *id del cliente*, una secuencia de caracteres que necesitamos para poder usar el servicio desde el código JavaScript del navegador y desde el código en Node.js del servidor. Para ello accede en la consola web de Google Cloud Platform a la opción :guilabel:`Credenciales` dentro del menú :guilabel:`APIs y servicios`. Elije crear una credencial de tipo :guilabel:`ID de cliente de OAuth`. En la nueva pantalla, escoge *web* como tipo de aplicación, introduce un nombre para la aplicación, y en :guilabel:`Orígenes de JavaScript autorizados` indica los URLs desde los que harás peticiones: normalmente, indicarás un URL del tipo ``http://localhost:5000`` para cuando la aplicación se lance en local y uno del tipo ``https://proyecto-10002.appspot.com/`` para cuando se despliegue en un servidor en la nube. No has de indicar nada en la sección :guilabel:`URIs de redirección autorizados`. Con esta información, ya podrás obtener el *id del cliente*. A continuación se describe una aplicación sencilla que permite que el usuario se identifique en el navegador con su cuenta de Google. La aplicación completa está en la carpeta ``code/gsignin`` del `repositorio de la asignatura`_ . Tras la autenticación, el código puede obtener una serie de datos del usuario entre los que es especialmente relevante el *token id*, que será el dato que se enviará al servidor y del que este extraerá un *id* del usuario que será el que se almacenará en las bases de datos para indicar el usuario asociado a un registro dado. Observa que no se envía al servidor un dato como la dirección de correo electrónico para usarlo como identificador del usuario porque podría cambiar en algún momento del futuro. Tampoco se le envía el *id* del usuario, sino un *token* más largo que codifica diferentes datos, incluyendo el *id* del usuario. Este token deberá ser validado por el servidor antes de dar por bueno el *id* que incluye. Este es el código de la parte del cliente: .. literalinclude:: ../../code/gsignin/public/index.html :language: html :linenos: El código del lado del navegador es el que lleva el mayor peso de la tarea. Como puedes ver, la librería de Google ``apis.google.com/js/platform.js`` simplifica gran parte del proceso. La función ``initGoogleAPI`` inicializa el objeto ``auth2`` y registra diferentes funciones de *callback* que serán invocadas cuando el usuario se logre autenticar pulsando el botón (``onSuccessSignIn``), cuando el paso anterior falle (``onFailureSignIn``) o cuando el usuario se identifique por cualquier medio (``signinChanged``). Observa que es posible que se invoque ``signinChanged`` sin invocar ``onSuccessSignIn``; por ejemplo, si un usuario se ha identificado previamente, no ha salido de la aplicación, pero cierra la página y vuelve a abrirla, entonces la librería autentica automáticamente al usuario y llama a ``signinChanged`` pero no a ``onSuccessSignIn``. Se incluye también una función (``signOut``) para salir de la aplicación cuando se pulsa el botón correspondiente; en este caso, también se llamará a ``signinChanged`` con el valor ``false`` como parámetro. El *token id* se obtiene con ``auth2.currentUser.get().getAuthResponse().id_token`` y se envía al servidor en la cabecera de HTTP ``Authorization`` con el prefijo estándar ``Bearer`` que identifica el tipo de autenticación. En el lado del servidor, hay pocas novedades reseñables: .. literalinclude:: ../../code/gsignin/app.js :language: javascript :linenos: Se ha añadido un *middleware* que obtiene el valor de la cabecera ``Authorization`` y lo verifica usando la función ``verifyIdToken`` del módulo de Node.js ``google-auth-library``. Del objeto devuelto se puede extraer el *id* del usuario con ``getPayload()['sub']`` y usar este valor como identificador del usuario en la base de datos (esta parte se ha omitido en el código, ya que ya se estudió en un apartado anterior). .. Note:: Si necesitas aclarar algún aspecto del código anterior, puedes consultar la referencia del `cliente de JavaScript de Google Sign-in`_, la guía `Integrating Google Sign-in into your web app`_, la `documentación de la librería de Google`_ o la descripción del `acceso con OAuth 2.0 a la API de Google`_. .. _`Integrating Google Sign-in into your web app`: https://developers.google.com/identity/sign-in/web/sign-in .. _`documentación de la librería de Google`: https://github.com/google/google-api-javascript-client .. _`acceso con OAuth 2.0 a la API de Google`: https://developers.google.com/identity/protocols/OAuth2 .. _`cliente de JavaScript de Google Sign-in`: https://developers.google.com/identity/sign-in/web/reference Con lo anterior, la autenticación se realiza almacenando los datos del usuario en un *token* que genera una vez el servidor y que el cliente envía como cabecera en cada petición posterior, lo que constituye un ejemplo de `autenticación sin sesión`_ (*session-less*). El *token* suele codificar la información siguiendo el estándar JWT (por *JSON web token*) y es validado por el servidor para cada petición. La comunicación entre el cliente y el servidor se ha de realizar encriptada mediante protocolos seguros como SSL/HTTPS para que terceros no puedan obtener el *token* y suplantar la identidad del usuario. Los enfoques de autenticación basados en sesión, por otro lado, almacenarían los datos del usuario en la memoria del servidor (lo que pude plantear problemas si la cantidad de usuarios es muy grande) y enviarían una *cookie* con el identificador de sesión al cliente, que a su vez la adjuntaría a cada petición. .. _`autenticación sin sesión`: https://dev.to/thecodearcher/what-really-is-the-difference-between-session-and-token-based-authentication-2o39 Términos de uso de las APIs web ------------------------------- Aunque no lo estudiaremos en esta asignatura, hay que tener en cuenta que existen en la web multitud de APIs disponibles para su uso desde aplicaciones de terceros, pero estas APIs suelen tener términos de uso (mira las condiciones de la `API de Twitter`_, por ejemplo) que es importante leer antes de decidirse a basar una determinada aplicación en ellas. .. _`API de Twitter`: https://developer.twitter.com/en/developer-terms/agreement-and-policy