Saltar al contenido

Mes: marzo 2020

Autenticación en Angular con JWT

Hola, aprovechando un poco el paro que estamos teniendo en Perú por un aislamiento que a decretado el estado me di un tiempo entre el trabajo remoto y otras responsabilidades para hacer una pequeña demo sobre el uso de autenticación en Angular, específicamente como podemos valernos de interceptores y guards para poder trabajar con json web tokens (JWT).

Hago la demo de este punto en especifico por que recibi un par de preguntas la semana pasada sobre este tema y me di cuenta que en su momento para mi también entender ciertos conceptos y como se usaban me costo un poco y decidí hacer una demo y un post sobre esto.

Para explicar un poco como he decidido dividir la demo, el proyecto en angular se ha desarrollado desde 0, valiéndome de angular cli para generar la estructura básica de angular y sobre esta cree dos componentes una publico y otro protegido además de otras carpetas para los intercetores, guard y servicios. Del lado del backend, para generar nuestros JWT me estoy valiendo de una aplicación que ya tengo realizada y sólo he modificado el endpoint para generar el token y retornarlo. Veamos con más detalle nuestra aplicación en angular.

Estructura del proyecto en angular

Veamos que tenemos en nuestro «app.module»:

Configuración en AppModule
  • declarations: Se declaran los dos componentes que vamos a usar en nuestra demo el público y el privado.
  • imports: Importamos «HttpClientModule» para poder realizar conexiones http, «FormsModule» y «ReactiveFormsModule» para poder crear nuestro formulario de login. Los otros dos imports se añaden automaticamente al generar el proyecto.
  • providers: Acá estamos registrando nuestro servicio de autenticación y además registramos nuestros interceptor.

Ahora revisemos el «app-routing.module.ts»:

Configuración del AppRoutingModule

Lo único que debe importarnos acá es el registro de los routes, como podemos ver tenemos un array con dos objetos uno de ellos es nuestro componente público al cual estamos direccionando la ruta por defecto y el otro objeto es para nuestra componente protegido, en el cual es necesario indicar que para activarse (canActivate) se usará nuestro guard que se detallará más adelante.

Rapidamente, revisemos los componentes que se han creado:

Public

Configuración en el componente public
Configuración de la vista del componente public

Veamos un que tenemos en este componente. Declaramos un formulario de login típico que al realizar el submit llama al método «onSubmit()», tenemos la inidicalización de nuestro formulario y en el submit se valida si se ingresaron valores de usuario y contraseña y de ser esto así llama al servicio de login. Si este último llamado es correcto nos subscribimos a la respuesta para poder guardar el token en el localstorage y hacemos un navigate con el route a nuestro componente protegido «protected».

Protected

Configuración del componente protected

Este componente es literalmente un «ng g c protected». Dado que la demo se centra en llegar a el, no en lo que este nos va a mostrar. Veamos que sucede cuando en nuestro componente public se ejecuta la linea:

this.router$.navigate(['/protected']);

Se vemos el código, esta linea se ejecuta si y sólo si la petición que se realizo al backend por el token fue correcta y antes de realizarse se graba este token en el localstorage del browser. Cuando se ejecute, es donde entra a tallar nuestro guard. Veamos que lógica se tiene ahí.

Configuración del guard

Lo que tenemos acá es una clase creada por notros que implemente «CanActivate» y en esta implementación lo que yo estoy haciendo es intentando obtener el token que se debío grabar si la autenticación es correcta, si se obtuviera entonces retorno true y esto hace que angular nos redirija a nuestra url solicitada. De no ser asi, le estoy indicando que envíe al usuario a la ruta básica (public para nuestro caso) y que retorne false.

Revisemos para finalizar el consumo del servicio de autenticación y el interceptor.

Servicio de autenticación

Lo interesante acá es el interceptor ya se explico donde debemos registrarlo, ahora veamos como se ha implementado y que nos permite realizar.

Implementación interceptor parte I
Implementación interceptor parte II

Veamos como en el constructor de nuestro interceptor nos subscribimos a los cambios en el route, se valida el tipo de evento y luego se le indica que siga avanzando y termina entrando en nuestro método implementado «intercept» en este método validamos la url que se intenta consumir con una expresión regular, luego de verifica si existiera un token en nuestro localstorage, de ser este es el caso se clona el request inicial y se le añade el header de «Authorization» con el bearer que corresponde. Del mismo modo, se añade el header del «Content-Type» y por úlitmo siempre se añade el header del formato de la data esperada con el «Accept». Además, incrementamos los request pendiente de realizar con el «pendingRequests» y finalmente en el return le hacemos un pipe al observable que va a retornar de la solicitud. Cuando esta solicitud sea exitosa entrará a nuestra subscripción en nuestro componente que ya revisamos previamente, en el caso contrario entrará al «catcherror» y acá simplemente haremos un throwerror. Consideremos que siempre entrará al método «finalize» y es acá donde reducimos nuestras request pendientes por que ya se realizo.

Ya para terminar con este post – ahora si de verdad – veamos que hace nuestro backend.

Backend para generar el JWT

Como se puede observar, se a modificado un poco el funcionamiento del método para que basicamente reciba un usuario y contraseña, genere un token y lo retorne.

Con esto terminamos la demo y aunque el post salio un poco más extenso de lo que hubiera querido, me parece que se cubre todo a detalle. El funcionamiento de la demo pueden revisarla el video que adjunto y como siempre dejo también acceso al repositorio donde pueden encontrar el código fuente del frontend.

REPO: GITHUB

Saludos.

2 comentarios

Web API NetCore 3.0 con EF

Esta semana me toco estar en una reunión donde entre otras cosas se discutía el desarrollo de un nuevo microservicio que permita suplir alguna necesidad del negocio, y mientras se exponían puntos de vista sobre que deberíamos considerar, se tuvo un debate ameno sobre el uso de un ORM para tener acceso a datos en una base de datos relacional. Esto me llevo a querer hacer una demo sobre como funciona entity framework con NetCore 3.0, como se debería configurar para conectar a una base de datos y poder obtener información.

Así nace este post, tenemos un proyecto básico que nos permite conectarnos a una base de datos en MySQL, y que con EF podemos tener acceso a su información. Aproveche para configurar algunas cosas adicionales en el proyecto que también voy a explicar.

Primero quisiera comentar que mi historia con EF, no siempre fue la mejor. Cuando inicie en mi primer trabajo como practicante recuerdo que el sistema que tenían usaba ADO.Net con la ejecución de procedimientos almacenados, y en ese tiempo el lider técnico era un evangelizador de los beneficios que brindaba poder modificar los procedimientos sin necesidad de tener que realizar un deploy de la aplicación. Y asi es como yo inicialmente no tuve mucho contacto con EF, en trabajos venideros empece a aprender más sobre bases de datos, sobre distintos tipos de ORM, y poco a poco me fui interesando sobre el potencial que tenía EF, hasta que llego un proyecto en el que pude verlo en extenso y ser testigo de todo el potencial que esconde, claro con los problemas que también puede acarrear.

Digo problemas, por que si es mal usado puede devenir en un ineficiente manejo de la base de datos, todo depende de como lo usen los desarrolladores y que prácticas se sigan. Ahora si, veamos el proyecto.

Partimos ejecutando el siguiente script de la base de datos, que pueden encontrar en la carpeta scripts del repositorio.

Scripts

Veamos un poco el diagrama entidad relación de esta base de datos de demo.

Diagrama E-R

Como podemos ver, tenemos un diagrama con 12 tablas, entre las cuales de modo básico tenemos el manejo de las direcciones de una persona, y los estudiantes. Veremos más a detalle como se vincula la información en el video. Revisemos, ahora el proyecto.

Configuración del proyecto

Repasemos cada una de las carpetas y como se han dividido.

  • Configuration: Acá he puesto algunos archivos de para el correcto funcionamiento del proyecto, el archivo «ContainerProvider.cs» es para la inyección de dependencia, «ExceptionMiddleware.cs» nos permite capturar alguna excepción no controlada que se pueda generar en la ejecución de la aplicación y centralizar las acciones que tomen y finalmente «SwaggerServiceExtensions.cs» que nos permite realizar la configuración de swagger para generar documentación en nuestra API.
  • Context: En esta carpeta, se tiene el archivo «EFCoreContext.cs» que nos permite configurar entity framework, las relaciones entre las distintas tablas entre otras cosas.
  • Controller: Acá irán todos los controladores que necesitemos exponer en nuestra API.
  • Entity: La entidad que obtendremos de la base de datos.
  • Mapper: Estamos usando automapper en el proyecto, en esta carpeta pondremos los distintos profiles que requiramos crear para realizar el mapeo de nuestra información.
  • Model: En esta carpeta pondremos todas los DTO, que se expondrán como información por nuestro API.
  • Repository: Esta carpeta será nuestro repository, para el cual tenemos la interfaz, y su implementación.
  • Script: Acá estoy dejando ciertos archivos importantes para poder levantar la demo, el script de base de datos, diagrama E-R, etc.
  • Service: Acá pondremos la lógica de negocio que consideremos necesaria. También tiene la interfaz y su implementación.
  • Transversal: Pondremos acá distintas utilidades que podemos usar en el proyecto.

Para no entrar en detalle de la configuración en EF de todas las entidades, tomemos como modelo a la entidad «person».

Configuración de la entidad person

Como podemos ver, nos valemos de las «DataAnnotations» que nos permitan indicar el nombre de la tabla que se va a mapear a una entidad, del mismo modo con las columnas, en caso posean un nombre distinto en la base de datos. Mediante estas anotaciones es posible especificar más información, como las relaciones entre las tablas o las llaves primarias y foraneas que se tienen. Sin embargo, particularmente prefiero indicar estas relaciones en el context.

Veamos que configuración se tiene en este archivo.

Configuración contexto

Como podemos ver, es necesario realizar un override al método «OnModelCreating» y ahí especificar las relaciones entre las distintas tablas, así como también las llaves foraneas. Entre otro tipo de configuración con la base de datos que sea necesario realizar. Revisemos ahora el repositorio y como estamos obteniendo la información.

Llamada al contexto

Tenemos una interfaz que declara un método asyncrono que permite obtener el listado de estudiantes y en su implementación hacemos uso de EF para obtener esta información con los joins que son necesarios para nuestra demo. En la capa de servicio, tenemos los siguiente.

Servicio

Tenemos la implementación de la interfaz para la capa de servicio, como se puede ver, se obtiene del repositorio la información y luego se realiza un mapping de estos campos para el DTO que terminaremos devolviendo en el API. Notemos que no tenemos ningún try-catch hasta ahora en nuestro código, esto dado que se a implementado un middleware que nos permita centralizar los errores. Veamos como se implemento esto.

ExceptionMiddleware

Si ocurriera algún error en la ejecución de nuestra solicitud, se terminará entrando en nuestro método «HandleExceptionAsync» acá podríamos tomar las acciones necesarias, para la demo estoy devolviendo un error genérico que un GUID autogenerado por cada error.

El proyecto no es nada del otro mundo, con respecto a lo que ya hayamos visto con net core, pero me parecío interesante hacer una guía sobre como usar EF. Pueden revisar el video en donde seguro hablo a más detalle sobre algunas cosas de la implementación del proyecto, el proyecto demo también esta en github por si quieres revisarlo y si hubiera alguna duda adicional pueden dejarme una pregunta.

REPO: GITHUB

Saludos.

3 comentarios