DEV Community

Cover image for Autenticación en Angular con Auth0
Dan Espinoza
Dan Espinoza

Posted on

Autenticación en Angular con Auth0

Hola, esta es la segunda parte del tutorial donde intentaremos conectar una SPA en Angular con un servicio creado en .Net core y añadir autenticación y autorización con Auth0.

Si aún no viste la primera parte puedes encontrarla aquí

En la primera parte creamos tanto el proyecto en angular como el de .Net Core y realizamos la comunicación entre ellos, pero nos quedamos con un problema de seguridad que a continuación resolveremos.

Así que comencemos.

Configurando Tenant de Auth0

Ya vamos avanzando, daré por hecho que en este momento ya cuentas con una cuenta de Auth0. Si no es así ¿Que esperas? es gratis! bueno de pende que plan elijas 😂.
https://auth0.com/

Una vez creada la cuenta nos pedirá que configuremos un Tenant, basta con asignar un nombre y una región. Dependiendo de tu criterio y de la protección general de regularización de datos elige la región que mas te guste. a continuación damos en CREATE

image

Listo! tenemos nuestro Tenant creado. Es momento de crear nuestra aplicación y la API.

lo primero que haremos será configurar la protección de nuestra API, para ello daremos click en APIs que se encuentra en el menu lateral dentro de Applications

image

Crearemos una nueva API dando click en el botón + CREATE API

image

En la pantalla de creación elegiremos un nombre que sea amistoso y fácil, tambien necesitamos dar un Identifier que por recomendación nos piden que sea una url, no es necesario que esta sea publica simplemente es para identificarla. Finalmente un algoritmo para generar los tokens utilizaremos en este caso RS256. Clicamos en CREATE

image

A continuación damos click en la API que acabamos de crear, vamos a la pestaña settings y en el apartado General Settings copiamos el Id. Lo mantendremos copiado y lo utilizaremos más adelante.

image

image

Listo hemos configurado nuestra API, ahora creemos nuestro Aplicación. Demos click en Applications que se encuentra en el menu lateral dentro de Applications

image

A continuación Click en +CREATE APPLICATION

image

Elegimos un nombre para distinguir nuestra aplicación y seleccionamos el tipo de aplicación Single Page Web Applications y damos click en CREATE.

image

Listo tenemos la aplicación creada, configuremos solo una cosa mas en la pantalla de nuestra nueva aplicación. Seleccionemos la pestaña de settings.

image

en el apartado de Application URIs agregamos la uri de nuestro cliente angular http://localhost:4200/ a los siguientes campos

  • Allowed Callback URLs
  • Allowed Logout URLs
  • Allowed Web Origins
  • Allowed Origins (CORS)

y guardamos los cambios.

image

al final de la pagina de configuración damos click en Advanced Settings y posteriormente en la pestaña OAuth.

En el campo Allowed APPs / APIs pegamos o escribimos el Id de la API que creamos anteriormente. Sí, el que te dije que copiaras y lo mantuvieras en tu portapapeles. Si no lo recuerdas ve a la sección APIs > Click en la API que creamos > Click en Settings. Se encuentra en la sección General Settings en el campo Id.

image

Demos click en SAVE CHANGES. Terminamos de configurar Auth0... por ahora.

De vuelta al código

Implementemos un sistema de login en nuestro proyecto de angular con Auth0. Para ello vamos a nuestra consola dentro da la raíz del proyecto e instalemos el sdk que nos proporciona Auth0

npm install @auth0/auth0-angular
Enter fullscreen mode Exit fullscreen mode

Una vez instalado el paquete necesitamos primero recuperar el dominio y el clientId que registramos en la aplicación cliente en el Dashboard de Auth0. Si no las recordamos podemos encontrarlas en el menú Applications ⇒ Applications ⇒ ⇒ settings

image

Una vez que nos hagamos de ellas agregaremos algunas líneas de código al app.module.ts quedando de la siguiente manera

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AuthModule } from '@auth0/auth0-angular';
import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    AuthModule.forRoot({
      domain: 'Domain',
      clientId: 'ClientID'
    }),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

Acabamos de importar al modulo principal Auth0Module y lo configuramos para que funcione con las credenciales de nuestra aplicación. Asegúrate de cambiar Domain y ClientId por los de tu aplicación SPA configurada anteriormente.

Crearemos un nuevo componente que se encargará de realizar el login y logout de nuestra aplicación

ng g c components/auth-button
Enter fullscreen mode Exit fullscreen mode

El comando anterior genero un componente llamado auth-button.component agreguemos funcionalidad a este componente

// ### auth-button.component.ts ###

import { DOCUMENT } from '@angular/common';
import { Component, Inject, OnInit } from '@angular/core';
import { AuthService } from '@auth0/auth0-angular';

@Component({
  selector: 'app-auth-button',
  templateUrl: './auth-button.component.html',
  styleUrls: ['./auth-button.component.scss']
})
export class AuthButtonComponent implements OnInit {

  constructor(@Inject(DOCUMENT) public document: Document, public auth: AuthService) {}

  ngOnInit(): void {

  }

}
Enter fullscreen mode Exit fullscreen mode
<!-- ### auth-button.component.ts ### -->

<ng-container *ngIf="auth.isAuthenticated$ | async; else loggedOut">
  <div class="profile-container">
    <div class="profile" *ngIf="auth.user$ | async as user">
      <img class="img-profile" *ngIf="user.picture" [src]="user.picture" alt="image profile">
      {{user.name}}
    </div>
    <button class="btn btn-red" (click)="auth.logout({ returnTo: document.location.origin })">
      Log out
    </button>
  </div>
</ng-container>

<ng-template #loggedOut>
  <button  class="btn btn-red" (click)="auth.loginWithRedirect()">Log in</button>
</ng-template>
Enter fullscreen mode Exit fullscreen mode

Lo que acabamos de hacer es definir un botón que se encargara de hacer la petición a Auth0 para realizar el login del usuario mediante el método auth.loginWithRedirect() , al dar click redirigirá al usuario al Universal Login Page de Auth0 para poder registrarlo y autenticarlo. Una vez autenticado Auth0 redireccionara a la url que definimos anteriormente [http://localhost:4200](http://localhost:4200) .

image

image

image

Una vez autenticados en nuestra aplicación, podremos observar un poco de la información del usuario activo gracias al observable atuth.user$ , este observable tiene información sensible del usuario autenticado. Cabe aclarar que este observable solo emitirá su valor si isAuthenticate$ es true a demás de la información del usuario, veremos que el botón login ha cambiado a logout.

image

Esto es gracias que estamos utilizando isAuthenticated$ para validar si el usuario está autenticado, ahora al hacer click en Logout para salir se llamará al método logouth() quien se encargara de terminar la sesión del usuario. Es importante saber que le estamos diciendo al método logouth() que redireccione a http://localhost:4200 que definimos en la sección de Allowed Logout URLs en el dashboard. Solo que estamos haciendo uso de window.location.origin para indicar lo mismo.

Listo! Pudimos configurar el cliente para que permita el registro e inicio de sesión, pero si hacemos click en los botones para llamar a los servicios de backend notamos que funcionan aun cuando no se haya iniciado sesión. De los tres servicios que tenemos solo queremos que uno sea público, los otros dos botones no deberían retornarnos una respuesta (200OK) a menos que tengamos iniciada la sesión. Vamos a solucionar esta problemática.

Configurando autenticación en backend mediante JWT

A continuación protegeremos la API mediante la validación de un token emitido por Auth0, para eso haremos uso de una librería que nos ayudara con eso.

La libreria en cuestion es Microsoft.AspNetCore.Authentication.JwtBearer y la podemos instalar de 2 formas distintitas. La primera mediante el administrador de paquetes nugget en Visual Studio y la otra con el CLI de .Net.

Para instalarla con el CLI de .NET basta con ir a la rais de nuestro proyecto de .Net y ejecutar mediante la consola lo siguiente

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Enter fullscreen mode Exit fullscreen mode

La otra opción es ir al Administrador de paquetes.

En Visual Studio dar click derecho sobre el proyecto > Administrar Paquetes Nuget...
Se abrirá una pestaña dar click en examinar y buscar el paquete por su nombre, posteriormente dar click en instalar y aceptar las dependencias y licencias.

image

// ### TestController.ts ###

// Código existente...
public class TestController : ControllerBase
{
    [HttpGet("public")]
  public IActionResult GetPublic()
  {
      var result = new Result("Se llamó al servicio publico de manera satisfactoria.!");
    return Ok(result);
  }
  [Authorize]
  [HttpGet("private")]
  public IActionResult GetPrivate()
  {
      var result = new Result("Se llamó al servicio privado de manera satisfactoria.!");
    return Ok(result);
  }

  [Authorize]
  [HttpGet("permission")]
  public IActionResult GetPermission()
  {
      var result = new Result("Se llamó al servicio privado con permisos de manera satisfactoria.!");
    return Ok(result);
  }
}
// Código existente...
Enter fullscreen mode Exit fullscreen mode

Ejecutamos la solución y probamos los servicios en postman o tu entorno de desarrollo de APIs y pruebas favorito.

image

image

image

Notamos como el estatus de la respuesta es un 401 Lo que significa que no tiene autorización para ver este contenido.

Excelente, hemos conseguido poner seguridad y evitar el acceso a usuarios no autorizados. Ahora probemos con nuestra aplicación en angular.

Si haz iniciado sesión, es momento de que hagas logout y finalices la sesión del usuario. Si es así prueba a pulsar los botones a ver que pasa.

image

Muy bien, observamos que esta aplicada la seguridad en nuestros endpoints. Ahora inicia sesión y prueba nuevamente.

image

image

🤔 Umm... Parece que algo no esta bien, deberíamos tener acceso al menos a la api privada ya que estamos logueados.

¿Que Pasó?

JWT (Json Web Token) eso es lo que pasó. ¿O no pasó?.

No voy a profundizar mucho en lo que es JWT y prometo hacer un articulo dedicado a eso, pero en pocas palabras un JWT es un JSON codificado en Base 64 que emite un servidor para verificar la identidad de un usuario que intenta acceder a un recurso. Cuando un usuario quiere acceder a un recurso mediante el protocolo HTTP debe añadir en su petición ese JWT codificado mediante la autenticación bearer. es una cadena mas o menos así:

bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im1GS0hoVGdJdDVKZUdxLWtWLXZOcCJ9.eyJpc3MiOiJodHRwczovL2hlYWRsZXNzLWFwaS51cy5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjA1Y2JjYjJlZDNlMjgwMDZmNmQyZTI2IiwiYXVkIjpbImh0dHA6Ly9tb25nb2RlYmFwaS5jb20iLCJodHRwczovL2hlYWRsZXNzLWFwaS51cy5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNjE2Njk0NzkzLCJleHAiOjE2MTY3ODExOTMsImF6cCI6Ijl1S3A0T2dvdkRLQTBZM2E3eDV1MXdhWDhvcGpDa3dtIiwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsImd0eSI6InBhc3N3b3JkIn0.SuL3hCo9mKM8SwV-CsRwRkP7jHL6_26-wCO4gzIjyhV89356dugoFtPa_hRPfO2HyT8tyrGI2SCytdS8tcsbqjpLDbZ8AO2b1TbJgt1Wedq8dPgF1uYsmHp-VrKOdFbQK2824kNzBVuOPEzSkgX7v2KQdBtyAlHcyuEMjaT7sSqxeq0acosJGeJ7pvYyz-Tsy6AcgNVOst3RZNrwYoqzNcBey__53HqyEFku-cblpVuMBOqq1snDgdq24rxjRHnVWNvxqwmDWu1wIwyNHzAUnEoJttHJB84aZtX8XAZvQ9zG1QHLfxwB4sB87JQz2UY1jgWxeio0hvBe0OqR_nGgxw
Enter fullscreen mode Exit fullscreen mode

una vez que el servidor de recursos api recibe una petición, revisa que en las cabeceras venga ese token, de ser así lo valida con el servidor que lo emitió, si el Token es valido, no fue alterado ni ha expirado, el servidor de recursos Api permite que la petición pase, de lo contrario no lo hace y retorna un error 401 Unauthorize.

De vuelta al proyecto de angular. Si recordamos, en ningún momento estamos recuperando ese token ni tampoco lo hemos incluido en las peticiones a las api que estamos llamando. Afortunadamente Auth0 también provee una solución sensilla de configurar un interceptor así que solucionemos eso.

// ### app.module ###

// Código existente...
import { AuthHttpInterceptor, AuthModule } from '@auth0/auth0-angular';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

// Código existente...
 AuthModule.forRoot({
  // Código existente...

    // configuración del interceptor
  httpInterceptor: {
      allowedList: [{
          uri: 'https://localhost:44386/api/v2/*',
      tokenOptions: {
          audience: 'audience asignada a tu api en el dashboard de Auth0',
          }
       },
         {uri:'https://localhost:44386/api/test/private'},
     {uri:'https://localhost:44386/api/test/permission'}
        ]
   }
    // termina configuración del interceptor

 }),
],
  providers: [
        { provide: HTTP_INTERCEPTORS, useClass: AuthHttpInterceptor, multi: true }
    ],
// Código existente...
Enter fullscreen mode Exit fullscreen mode

Agregamos una configuración al array de los providers y le decimos que configure como de tipo HTTP_INTERCEPTOR el cual proviene de AutHttpInterceptor asegúrate de agregar la clase a tus imports.

dentro de la configuración de AuthModule agregamos un interceptor donde indicamos la lista de sitios que funcionaran con este interceptor, aqui solo hemos configurado uno y le indicamos que le añada el token a las urls que coincidan con la uri indicada. En la primera regla que puse como ejemplo, le decimos en este caso que apunte a las url de nuestro backend. Observa como finaliza con * el cual es un comodín que le indica que todas las urls que inicien con lo que esta antes de el * deberá añadir el token.

ESTA REGLA NO LA VAMOS A EMPLEAR EN ESTE TUTORIAL solo la puse para que sepas que puedes forzar la inclusion de todas las url que coincidan con un patron.

Añadiremos únicamente las url que queremos proteger, por seguridad siempre es recomendable mandar el token a las urls solo cuando es necesario, si una api es publica no deberíamos de mandar el encabezado con un token.

Existe otro modo de trabajar con estas reglas pero por ahora solo veremos esta forma.

Antes de continuar modificaremos la política de seguridad en nuestro servidor backend para que acepte el encabezado de autorización de lo contrario recibiremos un error de cross origins.

En la clase Startup.cs modificamos el middleware AddCors() para que quede de la siguiente forma

// ### Startup.cs ### 
// Código existente ..
services.AddCors(options =>
{
    options.AddPolicy(name: AngularClient, builder =>
  {
      builder.WithOrigins("https://localhost:4200", "http://localhost:4200");
    builder.WithHeaders("authorization");
   });
 });
// Código existente ...
Enter fullscreen mode Exit fullscreen mode

Una vez realizados estos cambios, probemos nuestra aplicación de dos maneras, sin sesión y con una sesión activa.

image

Observemos que cuando hacemos las peticiones a los servicios cuando la sesión no esta iniciada, llama de manera satisfactoria a nuestro servicio publico. Sin embargo cuando queremos llamar los servicios protegidos, el interceptor se encarga de decirnos que para poder utilizar esos servicios requerimos tener una sesión iniciada, ya que si no inicia sesión no existe un token para ser añadido a la cabecera de la petición.

Si notamos en la herramientas de desarrolladores la petición hacia los servicios private y permission ni siquiera fueron enviadas.

image

Con sesión activa

image

image

Por otro Lado cuando la sesión está iniciada, AuthModule puede generar un token y enviarlo en los request que definimos en la configuración. Por lo tanto obtenemos una respuesta 200 OK en cada uno de nuestros servicios solicitados.

Vamos a dejarlo por hoy terminemos para una tercer entrada la finalización de este tutorial y que cada vez se pone mas interezante.

Nos metermos de lleno a configurar y crear las politicas de autorización para permitir acciones a ciertos usuarios que cumplan con un cierto rol.

Así que hasta la otra.

Si aún no viste la primera parte puedes encontrarla aquí

Top comments (0)