Advertisement
  1. Code
  2. Angular

Autenticación basada en token con AngularJS y NodeJS

by
Read Time:18 minsLanguages:

Spanish (Español) translation by Nadia Castelli (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

La autenticación es una de las partes más importantes de una aplicación web. En este tutorial, discutiremos los sistemas de autenticación basada en token y cómo se diferencian de los sistemas tradicionales de login. Al final de este tutorial, verás una demo funcionando por completo, escrita en AngularJS y NodeJS.

Sistemas de Autenticación Tradicionales

Antes de proceder con un sistema de autenticación basada en token, vamos a echarle un vistazo a un sistema de autenticación tradicional.

  1. El usuario provee un nombre de usuario y contraseña en el formulario de login y clickea Ingresar.
  2. Una vez hecha la petición, se valida el usuario en el back-end mediante una consulta a la base de datos. Si la petición es válida, se crea una sesión utilizando la información de usuario brindada por la base de datos, y luego se retorna la información de la sesión en el encabezado de la respuesta, para así almacenar el ID de sesión en el navegador.
  3. Se provee la información de sesión para acceder endpoints restringidos de la aplicación.
  4. Si la información de sesión es válida, se permite al usuario acceder endpoints específicos, y se responde con el contenido HTML renderizado.

Todo marcha bien hasta este punto. La aplicación funciona, y es capaz de autenticar usuarios para que puedan acceder endpoints restringidos; sin embargo, ¿qué pasa cuando quieres desarrollar otro cliente, por ejemplo Android, para tu aplicación? ¿Serás capaz de utilizar la aplicación actual para autenticar clientes móviles y servir contenido restringido? Así como está ahora, no. Hay dos motivos principales por los cuales esto sucede:

  1. Las sesiones y las cookies no tienen sentido en aplicaciones móviles. No se pueden compartir sesiones o cookies creadas en el servidor con clientes móviles.
  2. En la aplicación actual, se devuelve el HTML renderizado. En un cliente móvil, se necesita un JSON o XML para incluir en la respuesta.

En este caso, necesitas una aplicación independiente del cliente.

Autenticación Basada en Token

En la autenticación basada en token, las cookies y las sesiones no se utilizan. Se emplea un token para autenticar al usuario en cada petición que se hace al servidor. Rediseñaremos el primer escenario usando autenticación basada en token.

El flujo de control será el siguiente:

  1. El usuario provee un nombre de usuario y contraseña en el formulario de login y clickea Ingresar.
  2. Una vez hecha la petición, se valida el usuario en el back-end mediante una consulta a la base de datos. Si la petición es válida, se crea un token utilizando la información de usuario brindada por la base de datos, y luego se retorna esa información en el encabezado de la respuesta, para así guardar el token en almacenamiento local.
  3. Se provee la información del token en el encabezado de cada petición para acceder endpoints restringidos de la aplicación.
  4. Si el token tomado del encabezado de la petición es válido, se permite al usuario acceder al endpoint especificado, y se responde con JSON o XML.

En este caso, no hemos retornado sesión ni cookie, y tampoco hemos retornado contenido HTML. Esto significa que podemos usar esta arquitectura para cualquier cliente en una aplicación específica. Aquí puedes ver el esquema de arquitectura:

Entonces, ¿qué es JWT?

JWT

JWT es la sigla de JSON Web Token y es un formato de token utilizado en encabezados de autorización. Este token es útil para diseñar la comunicación entre dos sistemas de manera segura. Vamos a renombrar JWT como el "token portador" para el propósito de este tutorial. Un token portador consiste de tres partes: encabezado, carga útil, y firma.

  • El header es la parte del token que almacena el tipo de token y el método de encriptación, que a su vez está encriptado en base-64.
  • La carga útil incluye la información. Puede contener cualquier tipo de datos como información de usuario, información de producto, etcétera, la cual está almacenada bajo encriptación base-64.
  • La firma consiste de combinaciones del encabezado, carga útil, y clave secreta. La clave secreta debe ser almacenada del lado servidor.

Debajo puedes ver el esquema JWT y un token de ejemplo:

No necesitas implementar el generador de token portador, ya que se pueden encontrar versiones ya existentes en varios lenguajes. Aquí puedes ver algunos:

Lenguaje URL de Librería
NodeJS http://github.com/auth0/node-jsonwebtoken
PHP http://github.com/firebase/php-jwt
Java http://github.com/auth0/java-jwt
Ruby http://github.com/progrium/ruby-jwt
.NET http://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet
Python http://github.com/progrium/pyjwt/

Un Ejemplo Práctico

Una vez cubierta un poco la información básica acerca de la autenticación basada en token, podemos proceder con un ejemplo práctico. Echa un vistazo al siguiente esquema, luego lo analizaremos con más detalle:

  1. Las peticiones son realizadas a la API desde varios clientes, como una aplicación web, un cliente móvil, etc., para un propósito específico.
  2. Las peticiones se realizan a un servicio como https://api.yourexampleapp.com. Si mucha gente utiliza la aplicación, pueden ser necesarios múltiples servidores para realizar la operación requerida.
  3. Aquí, el balance de carga se utiliza para balancear las peticiones, para que concuerden mejor con los servidores de aplicación en el back-end. Cuando se realiza una petición a https://api.yourexampleapp.com, el balance de carga atiende una petición primero, y luego la redirecciona a un servidor específico.
  4. Hay una aplicación, y esta aplicación es deployada a varios servidores (servidor-1, servidor-2, ..., servidor-n). Cada vez que se realiza una petición a https://api.yourexampleapp.com, la aplicación back-end intercepta el encabezado de la petición y extrae la información del token del encabezado de la autorización. Se realiza una consulta a la base de datos utilizando este token. Si el mismo es válido y tiene el permiso necesario para acceder al endpoint requerido, continúa. Si no, retorna un código de respuesta 403 (que indica un estado de prohibición).

Ventajas

La autenticación basada en token posee varias ventajas que solucionan problemas serios. Algunos de ellos son:

  • Servicios Independientes del Cliente. En la autenticación basada en token, el token se transfiere por medio de los encabezados de petición, en vez de almacenar la información de autenticación en sesiones o cookies. Esto significa que no existen estados. Se puede enviar una petición al servidor desde cualquier tipo de cliente que pueda realizar peticiones HTTP.
  • CDN. En la mayoría de las aplicaciones web actuales, las vistas se renderizan en el back-end y el contenido HTML se retorna al navegador. La lógica front-end depende del código back-end. Esta dependencia no es necesaria, ya que acarrea varios problemas. Por ejemplo, si estás trabajando con una agencia de diseño que implementa tu HTML, CSS, y JavaScript front-end, necesitas tomar ese código front-end y migrarlo hacia tu código back-end, para poder llevar a cabo operaciones de renderizado o carga de datos. Un tiempo más tarde, tu contenido HTML renderizado será muy distinto al implementado por la agencia. En la autenticación basada en token, puedes desarrollar un proyecto front-end separado del código back-end. Tu código back-end retornará un JSON en vez de HTML renderizado, y podrás colocar en el CDN la versión modificada y descomprimida del código front-end. Cuando navegues tu página web, el contenido HTML se servirá desde el CDN, y el contenido de la página se cargará mediante servicios de la API utilizando el token en los encabezados de autorización.
  • No Sesión Cookie (or No CSRF). CSRF es un problema mayor para la seguridad web moderna, porque no comprueba si la fuente de una petición es confiable o no. Para resolver este problema, se utiliza un banco de tokens para enviar dicho token en cada envío de formulario. En la autenticación basada en token, este se usa en los encabezados de autorización, y CSRF no incluye esa información.
  • Almacenamiento Persistente de Token. Cuando se realiza una lectura, escritura o borrado de sesión en la aplicación, se realiza una operación de archivo en la carpeta temp del sistema operativo, al menos la primera vez. Supongamos que tienes múltiples servidores y se crea una sesión en el primero de ellos. Cuando realizas otra petición y esta cae en otro servidor, la información de sesión no existe aún, entonces se obtiene la respuesta de "no autorizado". Lo sé, se puede resolver con una sticky session. Sin embargo, en la autenticación basada en token, esto se resuelve naturalmente. No existe el problema de la sticky session, porque el token de la petición se intercepta en cada petición, en cualquier servidor.

Estas son las ventajas más comunes de la autenticación y comunicación basada en token. Este es el final de la parte teórica y arquitectónica. Es hora de ver un ejemplo práctico.

Una Aplicación Ejemplo

Veremos dos aplicaciones para demostrar la autenticación basada en token:

  1. token-based-auth-backend
  2. token-based-auth-frontend

En el proyecto back-end, implementaremos servicios cuyos resultados serán en formato JSON. No se retornará ninguna vista en los servicios. En el proyecto front-end, tendremos un proyecto en AngularJS para el HTML front-end, y luego la aplicación front-end se cargará mediante servicios de AngularJS que harán peticiones a los servicios back-end.

token-based-auth-backend

En el proyecto back-end hay tres archivos principales:

  • package.json es para el manejo de dependencias.
  • models/User.js contiene un modelo User que se utilizará para las operaciones de usuario en la base de datos.
  • server.js para el arranque del proyecto y manejo de peticiones.

¡Eso es todo! Este proyecto es muy simple, para que puedas comprender fácilmente el concepto principal, sin sumergirte en profundidad.

package.json contiene dependencias para el proyecto: express para MVC, body-parser para simular el manejo de peticiones post en NodeJS, morgan para el registro de peticiones, mongoose para que nuestro framework ORM se conecte con MongoDB, y jsonwebtoken para crear tokens JWT mediante el uso de nuestro modelo User. Hay también un atributo llamado engines que indica que este proyecto está hecho con la versión >= 0.10.0 de NodeJS. Esto es útil para los servicios PaaS como Heroku. Cubriremos este tema en otra sección.

Dijimos que generaríamos un token usando la carga útil del modelo de usuario. Este modelo nos ayuda a realizar operaciones de usuario en MongoDB. En User.js, el esquema de usuario está definido y el modelo User se crea mediante un modelo mongoose. Este modelo está listo para operaciones de base de datos.

Nuestras dependencias y nuestro modelo de usuario están definidos, así que ahora los combinaremos para construir un servicio que maneje peticiones específicas.

En NodeJS, puedes incluir un módulo en tu proyecto usando require. Primero, necesitamos importar los módulos necesarios al proyecto.

Nuestro servicio atenderá mediante un puerto específico. Si alguna variable de puerto está definida entre las variables de entorno, se puede usar dicha variable; de lo contrario hemos definido el puerto 3001. Luego, se incluye el modelo User, y se establece la conexión a la base de datos para realizar algunas operaciones de usuario. No olvides definir una variable de entorno—MONGO_URL—para la URL de conexión a la base de datos.

En la sección de arriba hemos definido algunas configuraciones para simular una petición HTTP manejada en NodeJS mediante Express. Permitimos peticiones provenientes de diferentes dominios para así desarrollar un sistema con independencia del cliente. Si no permitiéramos esto, dispararíamos un error CORS (Cross Origin Resource Sharing, o compartición de recursos de distintos orígenes) en el navegador.

  • Access-Control-Allow-Origin permitido para todos los dominios.
  • Se pueden enviar peticiones POST y GET a este servicio.
  • Los headers X-Requested-With y content-type están permitidos.

Hemos importado todos los módulos requeridos y hemos definido nuestra configuración, así que es hora de definir los controladores de peticiones. En el código de arriba, cada vez que se realiza una petición POST a /authenticate con usuario y contraseña, se obtiene un token JWT. Primero, se procesa la consulta a la base de datos mediante un usuario y contraseña. Si dicho usuario existe, se retornan los datos del usuario y el token. Pero, ¿qué pasa si no hay coincidencia de usuario y/o contraseña?

Cuando se realiza una petición POST a /signin con usuario y contraseña, se crea un nuevo usuario con la información proporcionada. En la 19° línea, puedes notar que se genera un nuevo token JSON mediante el módulo jsonwebtoken, que fue asignado a la variable jwt. La parte de la autenticación está lista. Ahora, ¿qué pasa si intentamos acceder a un endpoint restringido? ¿De qué manera podemos acceder a dicho endpoint?

Al generar una petición GET a /me, obtendremos la información del usuario actual, pero para poder continuar con el endpoint solicitado se ejecutará la función ensureAuthorized.

En esta función, se interceptan los encabezados de la petición y se extrae el encabezado authorization. Si en este encabezado existe un token portador, dicho token es asignado a req.token para ser utilizado a lo largo de la petición, y la misma puede continuar mediante next(). Si no existe ningún token, obtendremos un error 403 (Prohibido). Regresemos al controlador /me, y usemos req.token para obtener los datos de usuario con este token. Cada vez que creemos un nuevo usuario, se genera un token y se guarda en el modelo de usuario en la base de datos. Dichos tokens son únicos.

Tenemos solamente tres controladores para este simple proyecto. Luego:

La aplicación NodeJS podría fallar si un error ocurre. Con el código anterior podemos prevenirlo, e imprimir un registro de error en la consola. Y finalmente, podemos iniciar el servidor utilizando el siguiente código.

En resumen:

  • Se importan los módulos.
  • Se realizan las confiiguraciones.
  • Se definen los controladores de petición.
  • Se define un middleware para interceptar endpoints restringidos.
  • Se inicia el servidor.

Hemos concluido el servicio back-end. Para que pueda ser usado por múltiples clientes, puedes publicar esta sencilla aplicación en tus servidores, o también puedes realizar un deploy en Heroku. En la carpeta raíz del proyecto hay un archivo llamado Procfile. Vamos a publicar nuestro servicio en Heroku.

Deploy en Heroku

Puedes clonar el proyecto back-end desde este repositorio GitHub.

No discutiremos cómo crear una aplicación en Heroku; puedes referirte a este artículo si no lo has hecho antes. Una vez creada la aplicación en Heroku, podemos agregar un destino al proyecto actual mediante el siguiente comando:

Ahora, ya hemos clonado un proyecto y agregado un destino. Luego de git addgit commit, podemos hacer un push a Heroku mediante git push heroku master. Una vez hecho el push, Heroku correrá el comando npm install para descargar las dependencias necesarias en la carpeta temp de Heroku. Luego, correrá nuestra aplicación y podremos acceder a nuestro servicio mediante el protocolo HTTP.

token-based-auth-frontend

En el proyecto front-end, verás un proyecto en AngularJS. Aquí mencionaré únicamente las secciones principales, porque AngularJS no es algo que pueda abarcarse en un sólo tutorial.

Puedes clonar el proyecto desde este repositorio GitHub. En él verás la siguiente estructura de carpetas:

Folder StructureFolder StructureFolder Structure

ngStorage.js es una librería para AngularJS, que sirve para manipular operaciones en almacenamiento local. También está el maquetado principal index.html y los maquetados parciales, que extienden el maquetado principal, en la carpeta partials. En controllers.js se definen nuestras acciones controladoras del front-end. En services.js se realizan las peticiones al servicio que creamos en el proyecto anterior. Tenemos además un archivo de arranque llamado app.js, y en él se aplican las configuraciones e importaciones de módulos. Finalmente, client.js es para servir archivos HTML estáticos (o simplemente el index.html, en este caso); esto nos ayuda a servir archivos HTML estáticos cuando hacemos un deploy en un servidor sin usar Apache u otro servidor web.

En el maquetado HTML principal se incluyen todos los archivos JavaScript requeridos por las librerías relacionadas con AngularJS, así como nuestro controlador personalizado, el servicio, y el archivo app.js.

En el código anterior, se define el controlador HomeCtrl y se inyectan algunos módulos requeridos, como $rootScope y $scope. La inyección de dependencias es una de las propiedades más fuertes de AngularJS. $scope es la variable puente entre controladores y vistas en AngularJS, esto significa que puedes usar test en una vista si la has definido como $scope.test = .... en un controlador específico.

En este controlador se definen algunas funciones útiles, por ejemplo:

  • signin para colocar un botón de sign in en el formulario de ingreso.
  • signup para controlar el formulario de registro.
  • me para asignarlo al botón Me en el maquetado.

En el maquetado principal, en la lista del menú principal, podrás ver al atributo data-ng-controller con el valor HomeCtrl. Esto significa que este elemento dom del menú comparte contexto con HomeCtrl. Al clickear el botón de registro en el formulario, se ejecutará la funcíón de registro en el controlador, que emplea el servicio de registro del servicio Main que ya está inyectado en el controlador.

La estructura principal es view -> controller -> service. Este servicio hace peticiones Ajax simples al back-end para obtener datos específicos.

En el código anterior, puedes ver funciones de servicios, por ejemplo peticiones de autenticación. Te habrás dado cuenta de que en controller.js hay funciones como Main.me. Este servicio Main se ha inyectado en el controlador, y en este, los servicios pertenecientes a este servicio son invocados directamente.

Estas funciones son simplemente peticiones Ajax a nuestro servicio, que hemos publicado juntas. No olvides asignarle la URL del servicio a la variable baseUrl. Al hacer el deploy del servicio en Heroku, obtendremos una URL de servicio como appname.herokuapp.com. En el código anterior, declararemos var baseUrl = "appname.herokuapp.com".

En la parte de registro o ingreso a la aplicación, el token portador responde a la petición, y este token se guarda en almacenamiento local. Cada vez que realicemos una petición a un servicio del back-end, debemos incluir este token en los encabezados. Podemos hacerlo mediante interceptores de AngularJS.

En el código de arriba, cada petición es interceptada, y se agrega un encabezado y valor de autorización en los encabezados.

En el proyecto front-end, hay algunas páginas parciales como signinsignupprofile details, y vb. Estas páginas parciales están relacionadas concontroladores específicos. Puedes ver esa relación en app.js:

Como podrás comprender leyendo el código anterior, cuando naveguemos a /, se renderizará la página home.html. Otro ejempo: si vamos a /signup, se renderizará signup.html. esta operaciónd e renderizado se realiza en el navegador, no del lado servidor.

Conclusión

En esta demo podrás ver en práctica todo lo que hemos discutido en este tutorial.

Los sistemas de autenticación basada en token ayudan a contruit un sistema de autenticación y autorización para servicios independientes del cliente. Mediante el uso de esta tecnología, podrás enfocarte únicamente en tus servicios (o APIs).

La autenticación y la autenticación serán controladas por el sistema de autenticación basada en token como una capa al frente de tus servicios. Podrás acceder y utilizar servicios desde cualquier cliente como navegadores web, Android, iOS, o un cliente de escritorio.

¡Sé el primero en conocer las nuevas traducciones–sigue @tutsplus_es en Twitter!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.