Autenticación en Angular con Auth0
Tags: auth0, angular, web, api
Published: 2021-04-02 04:16:47

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í](https://dev.to/odprz/angular-net-core-web-api-auth0-autorizacion-basada-en-politicas-695)

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/](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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q45dqirl96btfh0bsejz.png)
 

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pmbmvrz2pzaeb91jahd3.png)
 

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xoxczjqcsreqet15utru.png)
 
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rlldvx2agb4uh1kpmkrh.png)
 

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/caqs225ku9kzwu7zv6sx.png)
 

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l17kf49tncy3k08x5ul8.png)
 

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9v8mra1vib9bxbhwtwx7.png)
 
A continuación Click en +CREATE APPLICATION

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zlyepbk62t0jy7dwjc79.png)
 

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/97ohcudnd5ombtc95rye.png)
 
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qdexflzwo6nt62fffvy0.png)
 
en el apartado de **Application URIs** agregamos la **uri de nuestro cliente angular**  [http://localhost:4200/](http://localhost:4200/) a los siguientes campos

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

y guardamos los cambios.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ig75iv41tzsnbrbn1km3.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9l4os79zkx0zrq66vmb4.png)
 
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

```bash
npm install @auth0/auth0-angular
```

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 ⇒ <Nombre De la aplicación> ⇒ settings 

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lj3cxsb9769md8qvxxmp.png)
 

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

```tsx
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 { }
```

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

```bash
ng g c components/auth-button
```

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

```tsx
// ### 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 {

  }

}
```

```html
<!-- ### 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>
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/we99sgliarsfj5wou5uq.png)
 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28a8rbzsetp5lcd4i7vz.png)
 

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ipsg19orcmte9mqevyq6.png)
 

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/333mvv0fgxvy2sd6ksof.png)
 

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`](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

```bash
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4okjy9prsfnwxxbd5q6f.png)
 
```csharp
// ### 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...
```

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/crmgsh27j8kvzwvudg6c.png)
 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6vtrmnl679vzxlvrcsj.png)

 ![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7sbpgm1lr4uxutw2z4i0.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxen733lw6hxfdbekgui.png)
 
Muy bien, observamos que esta aplicada la seguridad en nuestros endpoints. Ahora inicia sesión y prueba nuevamente.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/481irmzpo89gfbcjuy7c.png)
 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrblxjburqbe9i0j7t5b.png)
 
🤔 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í:
 

```jsx
bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im1GS0hoVGdJdDVKZUdxLWtWLXZOcCJ9.eyJpc3MiOiJodHRwczovL2hlYWRsZXNzLWFwaS51cy5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjA1Y2JjYjJlZDNlMjgwMDZmNmQyZTI2IiwiYXVkIjpbImh0dHA6Ly9tb25nb2RlYmFwaS5jb20iLCJodHRwczovL2hlYWRsZXNzLWFwaS51cy5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNjE2Njk0NzkzLCJleHAiOjE2MTY3ODExOTMsImF6cCI6Ijl1S3A0T2dvdkRLQTBZM2E3eDV1MXdhWDhvcGpDa3dtIiwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsImd0eSI6InBhc3N3b3JkIn0.SuL3hCo9mKM8SwV-CsRwRkP7jHL6_26-wCO4gzIjyhV89356dugoFtPa_hRPfO2HyT8tyrGI2SCytdS8tcsbqjpLDbZ8AO2b1TbJgt1Wedq8dPgF1uYsmHp-VrKOdFbQK2824kNzBVuOPEzSkgX7v2KQdBtyAlHcyuEMjaT7sSqxeq0acosJGeJ7pvYyz-Tsy6AcgNVOst3RZNrwYoqzNcBey__53HqyEFku-cblpVuMBOqq1snDgdq24rxjRHnVWNvxqwmDWu1wIwyNHzAUnEoJttHJB84aZtX8XAZvQ9zG1QHLfxwB4sB87JQz2UY1jgWxeio0hvBe0OqR_nGgxw
```

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.

```jsx
// ### 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...
```

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

```csharp
// ### 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 ...
```

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6k4oe4hh2k9710dt1mjv.png)
 
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dnbsx4cpbk8jnxrtktsl.png)
 
Con sesión activa

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dnhmj0vwm1vnz35qsi5s.png)
 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e0d999bkjwz17z1tm9lf.png)
 
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í](https://dev.to/odprz/angular-net-core-web-api-auth0-autorizacion-basada-en-politicas-695)