DEV Community

loading...
Cover image for Angular + Net Core Web Api + Auth0 Autorización basada en políticas.

Angular + Net Core Web Api + Auth0 Autorización basada en políticas.

Oscar Daniel Perez Espinoza
Computer Systems Engineer 🛠, Frontend and Backend developer, 💻 Learning to change the world 🌐, Coding stuff with Angular and Tailwind. Musician 🎵🎸😜
・11 min read

Hola!. Esta es la primera entrada del tutorial donde te enseñare a registrar y autenticar a un usuario desde angular, consumir una web api desarrollada en .net core. Además gracias a Auth0 autenticaremos y validaremos que el usuario tenga suficientes privilegios para realizar dichas acciones.

Hoy en día, diseñar una aplicación con la más mínima funcionalidad requerirá persistir los datos; la mayoría de las veces nos veremos forzados a implementar de alguna manera, una función que nos garantice la autenticidad y validez de esos datos así como definir quien o quienes están destinados a verlos.

Controlando el acceso

Si no me has entendido aun, me refiero a implementar un método de seguridad que ayude a proteger y a restringir los accesos y modificaciones a un recurso.

Si bien, podríamos implementar desde cero un método de autenticación de usuarios y seguridad, no queremos reinventar la rueda. Afortunadamente existen muchas formas de solucionar el problema que nos planteamos.

Por ejemplo. Microsoft proporciona la Api ASP.Net Core Identity para permitir el control de usuarios en una aplicación Web (Administración de usuarios, contraseñas, claims, roles, tokens etc..). Pero cuando queremos proteger una SPA, App Móvil, Web api o un servidor de recursos, la recomendación de ellos es utilizar algún medio mas sofisticado para su protección. La verdad es que harán mas fácil y seguro todo el proceso.

Dentro de las recomendaciones que Microsoft menciona encontramos:

No te preocupes, también existen otros servicios de terceros que nos pueden ayudar a lidiar de una manera sencilla con este problema.

Auth0 al rescate

Hoy quiero hablar de Auth0. En sus propias palabras Auth0 es una plataforma adaptable de autenticación y autorización, fácil de implementar.

Auth0 es una solución de identidad basada en la nube, que ofrece una plataforma de autenticación y autorización que protege las identidades de cualquier tipo de usuarios dentro de una aplicación.

¿Que podemos hacer con Auth0?

  • Crear un sistema de registro y autenticación de usuarios para una aplicación mediante cuentas de usuario tradicionales (email y contraseña), con servicios de terceros (gmail, facebook, twitter etc..) así como con conexiones empresariales tales como AD, SAML, Google Workspace y otros.
  • Proteger servidores de recursos web api.
  • Usar cualquier base de datos de nuestra propiedad para el almacenamiento de usuarios.
  • Manejo de permisos basados en roles.
  • Entre otras funcionalidades avanzadas que permite la plataforma.

A demás cuenta con un plan gratuito que nos brinda un alcance de mas de 7,000 usuarios activos al mes entre otras características que para pequeños proyectos nos vendrá muy bien.

¿Que vamos a hacer?

Implementaremos una solución basada en Una SPA(Single Page Application) en Angular como capa de presentación.

Posteriormente conectaremos esa aplicación a un servidor de recursos en Net Core el cual consumiremos mediante servicios web api.

Finalmente integraremos esas aplicaciones con Auth0 que nos permitirá proteger el acceso a esos recursos. El acceso a la aplicación en angular sera mediante la autenticación de usuario y el acceso a los endpoints será mediante un JsonWebtoken. Solo los usuarios que tengan la autorización requerida podrán acceder a dichos recursos.

Manos a la obra

Pues dejemos atrás las palabras y entremos de lleno a ensuciarnos las manos.
Comenzaremos por crear tanto el proyecto de angular como el de net core. Para esto abriré una consola y utilizare el CLI de angular para crear una nueva aplicación

Daré por hecho que ya tienes instalado lo necesario para crear una aplicación de angular y de net core. De no ser así hay muchos tutoriales de los que puedes hacer uso y siempre tendrás las documentaciones oficiales.

#Creamos la aplicacion de angular
npx -p @angular/cli@latest ng new client-app-identity --style=scss
# o si tienes el cli instalado
ng new client-app-identity --style=scss 
Enter fullscreen mode Exit fullscreen mode

ya va de cada quien habilitar el modo estricto y configurar el routing en el momento que nos lo pregunte el cli. Cuestión de configurarlo a su criterio.

image

una vez creado el cliente seguiremos con la creación del proyecto de net core.

Al momento de escribir este articulo estoy utilizando Net Core 5.0 como framework

#creamos e ingresamos al directorio de la applicacion
mkdir backend-app-idnetity && cd backend-app-idnetity
#creamos una solucion para gestionar nuestro proyecto
dotnet new sln
#creamos un nuevo proyecto webapi
dotnet new webapi -n api-resource-server
#agregamos el proyecto a la solucion
dotnet sln add api-resource-server
Enter fullscreen mode Exit fullscreen mode

Tambien puedes crear un proyecto web-api de la manera tradicional entrando a Visual Studio y dando click en nuevo proyecto

image

image

Nunca esta de más saber todas las opciones que tenemos a la mano.

Volviendo a lo anterior, si observamos el directorio raíz encontraremos dos carpetas. Una es para nuestro proyecto de angular y otro el de net core

#regresamos al directorio raíz
cd ..
#revisamos el contenido del directorio 
#en bash
ll
#en CMD
dir 
Enter fullscreen mode Exit fullscreen mode

image

Abrimos la carpeta client-app-identity con tu editor favorito. Yo utilizare VS Code, este es nuestro proyecto.

Presionamos ctrl+ñ para lanzar la consola incluida con VSC y lanzamos nuestra aplicación

ng server -o
Enter fullscreen mode Exit fullscreen mode

Si todo sale bien Veremos nuestra aplicación inicial ejecutándose en nuestro navegador.

image

image

vamos a hacer algunas modificaciones a los siguientes archivos

// #### styles.scss ####

// para no centrarnos tanto en el diseño copiaremos el código 
// que se encuentra entre las etiquetas <style></style> en nuestro app.component.html
// y lo pegamos en este archivo
Enter fullscreen mode Exit fullscreen mode
<!--app.component.html -->

<div class="toolbar" role="banner">
  <img
    width="40"
    alt="Angular Logo"

  />
  <span>Angular + NetCore WebApi + Auth0</span>
    <div class="spacer"></div>
    <button style="margin-right: 2rem;">Login</button>
</div>

<div class="content" role="main">
  <h1>Bienvenido</h1>
</div>

<router-outlet></router-outlet>
Enter fullscreen mode Exit fullscreen mode

partiremos con estos cambios para tener la interfaz limpia. Deberíamos de ver este resultado

image

si observamos bien a demás de limpiar la vista añadimos en la parte superior derecha un botón para realizar el login en nuestra aplicación.

// #### style.scss ####
@import url('https://fonts.googleapis.com/css2?family=Roboto');
// ... Codigo anterior

.btn{
  padding: 8px;
  border-radius: 5px;
  border-width: 0 0 5px 0;
  border-style: solid;
  background-color: #c2c3c4;
}

.btn:hover{
  cursor: pointer;
}

.btn.btn-red{
  color: white;
  background-color: #F2385A;
  border-color: #d63452;
}

.btn.btn-red:hover{
  background-color: #eb2c4f;
  border-color: #d12d4b;
}

.btn.btn-blue{
  color: white;
  background-color: #3498DB;
  border-color: #258cd1;
}

.btn.btn-blue:hover{
  background-color: #2589cc;
  border-color: #1777b8;
}

.btn.btn-yellow{
  color: white;
  background-color: #dbbc34;
  border-color: #d1a925;
}

.btn.btn-yellow:hover{
  background-color:  #ceaf25;
  border-color: #c59c16;
}

.btn.btn-green{
  color: white;
  background-color: #34db3c;
  border-color: #39d125;
}

.btn.btn-green:hover{
  background-color: #2cc534;
  border-color: #32b820;
}

.apis-section{
  display: flex;
  width: 100%;
  flex-direction: column;
  row-gap: 1rem;
}
.result{
  padding: 1rem 0 .5rem 3rem;
  color: #24292e;
}

.profile-container{
  display: flex;
  justify-content: center;
  align-items: center;
  padding-right: 2em;
}
.profile{
  display: flex;
  justify-content: center;
  align-items: center;
  column-gap: 10px;
  margin-right: 10px;
}

.img-profile{
  width: 40px;
  height: 40px;
  border-radius: 50%;
}
Enter fullscreen mode Exit fullscreen mode

Lo que hicimos en styles.scss importar la fuente roboto desde la api de google.fonts y añadimos un poco mas de estilos al final de nuestro css para mejorar un poco los botones tambien añadimos una sección para organizarlos

<!--app.component.html -->

<!-- ... Codigo anterior -->
<div class="content" role="main">
  <h1>Bienvenido</h1>

<div class="apis-section">
    <div class="api">
      <button class="btn btn-blue" (click)="onPublicButtonClick()">Llamar Api Publica</button>
    </div>
    <div class="api" >
<button class="btn btn-yellow" (click)="onPrivateButtonClick()">Llamar Api Privada</button>
    </div>
    <div class="api">
      <button class="btn btn-green" (click)="onPermissionsButtonClick()" >Api privada + permisos</button>
    </div>
  </div>

<!-- ... Codigo anterior -->
Enter fullscreen mode Exit fullscreen mode

En app.component.html agregamos tres botones que invocan un método distinto

// ##### app.component.ts ####

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  onPublicButtonClick(){

  }

  onPrivateButtonClick(){

  }

  onPermissionsButtonClick(){

  }
}
Enter fullscreen mode Exit fullscreen mode

En app.component.ts añadimos tres métodos que llamaremos desde el evento click a los botones en nuestro template y desde los cuales haremos una llamada a nuestras api´s.

Ahora nuestra vista queda de la siguiente manera

image

Haremos una pausa en angular y pasaremos a generar nuestros servicios web api en el servidor de recursos en Net Core.

Generando los servicios en Net Core

Para comenzar abrimos la solución en Visual Studio, el explorador de soluciones se encuentra de la siguiente manera

image

Lo primero que haremos será crear 3 servicios, para esto daremos click derecho en la carpeta controllers y a continuación agregar>controller. Nos pedirá que indiquemos que tipo de controlador queremos y seleccionaremos Controlador de API: en blanco y lo nombraremos TestController.cs

image

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace api_resource_server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        [HttpGet("public")]
        public IActionResult GetPublic()
        {
            var result = new Result("Se llamó al servicio publico de manera satisfactoria.!");
            return Ok(result);
        }

        [HttpGet("private")]
        public IActionResult GetPrivate()
        {
            var result = new Result("Se llamó al servicio privado de manera satisfactoria.!");
            return Ok(result);
        }

        [HttpGet("permission")]
        public IActionResult GetPermissions()
        {
            var result = new Result("Se llamó al servicio privado con permisos de manera satisfactoria.!");
            return Ok(result);
        }
    }

    public class Result
    {
        public Result(string msg)
        {
            this.Msg = msg;
        }
        public string Msg { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Se crearon tres servicios los cuales accederemos desde nuestro cliente en angular, a demás se creo una clase de nombre Result que nos servirá para mapear nuestra respuesta.

Por el momento los tres endpoints son accesibles para cualquier persona que haga el llamado, más adelante vamos a restringir el acceso solo a ciertos usuarios con ciertos privilegios.

Antes de continuar, agregaremos una política de seguridad de orígenes cruzados para permitir la comunicación entre nuestro cliente y el backend.

Para esto modificaremos el archivo Startup.cs

// #### Startup.cs####

// Codigo existente ...

const string AngularClient = "angular-client";

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
   services.AddCors(options =>
   {
       options.AddPolicy(name: AngularClient, builder => builder.WithOrigins("https://localhost:4200", "http://localhost:4200"));
   });

// codigo existente ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// codigo existente ..
    app.UseCors(AngularClient);
// codigo existente ...
}
Enter fullscreen mode Exit fullscreen mode

Lo que hicimos fue agregar al middleware mediante services.addCors() una regla para que permita orígenes cruzados con la URL de nuestra aplicación angular.

y agregamos esa regla mediante app.UseCors()

Ahora podemos probar los servicios que acabamos de hacer, para esto podemos usar POSTMAN o cualquier aplicación de este tipo, la que más te guste.

En visual studio ejecutaremos el proyecto F5 o click en el botón ▶.

Lo primero que veremos sera la documentación de swagger junto con los endpoints que hemos creado.

image

Seguimos realizando las pruebas de los servicios.

image

image

image

Como lo mencione anteriormente los servicios funcionan pero cualquier persona puede acceder a ellos.

Conectando backend y cliente

Con los servicios creados y la plantilla que hicimos en Angular ya estamos listo para conectarlos así que manos a la obra.

Regresemos al proyecto de angular y realizaremos la siguiente modificación al código.

// ### app.module.ts ###

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

Añadimos a los imports de app.module.ts el modulo de HttpClient para poder realizar llamadas a servicios http. asegúrate de realizar la importación desde '@angular/common/http'

A continuación creamos una nueva carpeta de nombre models y dentro de ella un archivo llamado result.ts

image

El código quedaría de la siguiente forma

// ### result.ts ##

export interface Result{
  msg:string;
}
Enter fullscreen mode Exit fullscreen mode

este modelo servirá para poder mapear la respuesta de nuestro servicio.

Los otros archivos quedarían de la siguiente manera.

// ### app.component.ts ###

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Component } from '@angular/core';
import { Result } from './models/result';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  serverUri = 'https://localhost:44386/api/test/';

  headers: HttpHeaders = new HttpHeaders();

  publicResponse:Result;
  privateResponse:Result;
  permissionResponse:Result;

  constructor(private httpClient: HttpClient){
    this.headers.set('Content-Type','application/json');
    this.headers.set('Accept', 'application/json');
  }

  onPublicButtonClick(){
    console.log('response',this.publicResponse)
    this.httpClient.get<Result>(this.serverUri+'public',{headers:this.headers}).subscribe(
      result=>this.publicResponse = result,
      err=> this.publicResponse  = this.isUnauthorized(err)
    );
  }

  onPrivateButtonClick(){
    this.httpClient.get<Result>(this.serverUri+'private',{headers:this.headers}).subscribe(
      result=> this.privateResponse = result,
      err=>this.privateResponse = this.isUnauthorized(err)
    );
  }

  onPermissionsButtonClick(){
    this.httpClient.get<Result>(this.serverUri+'permission',{headers:this.headers}).subscribe(
      result=> this.permissionResponse = result,
      err=>this.permissionResponse = this.isUnauthorized(err)
    );
  }

    isUnauthorized(err:HttpErrorResponse):Result{
    if (err.status === 401 || err.status === 403){
      return {msg:'No tienes permisos para ver este contenido.'};
    }
    else{
      return {msg: err.error || 'Ocurrio un error en el servidor'};

    }
  }
}
Enter fullscreen mode Exit fullscreen mode

En app.component.ts se añadieron variables para guardar cada una de las respuestas de los servicios que se consuman. En cada uno de los métodos se agrego una petición al servicio mediante httpClient().

<!-- ### app.component.html ### -->

<div class="toolbar" role="banner">
  <img
    width="40"
    alt="Angular Logo"

  />
  <span>Angular + NetCore WebApi + Auth0</span>
    <div class="spacer"></div>
    <button style="margin-right: 2rem;">Login</button>
</div>

<div class="content" role="main">
  <h1>Bienvenido</h1>

  <div class="apis-section">
    <div class="api">
      <button class="btn btn-blue" (click)="onPublicButtonClick()">Llamar Api Publica</button>
      <div *ngIf="publicResponse" class="result">{{publicResponse.msg}}</div>
    </div>
    <div class="api" >
      <button class="btn btn-yellow" (click)="onPrivateButtonClick()">Llamar Api Privada</button>
      <div *ngIf="privateResponse" class="result">{{privateResponse.msg}}</div>
    </div>
    <div class="api">
      <button class="btn btn-green" (click)="onPermissionsButtonClick()" >Api privada + permisos</button>
      <div *ngIf="permissionResponse" class="result">{{permissionResponse.msg}}</div>
    </div>
  </div>

</div>

<router-outlet></router-outlet>

Enter fullscreen mode Exit fullscreen mode

En app.component.html agregamos un div que valida si tenemos una respuesta y muestra el mensaje.

Cuando hagamos click en cada uno de los botones se conectara a su respectivo servicio y mostrará la respuesta que entregue ese servicio.

Si todo sale bien al hacer clic en los botones, la vista se verá de la siguiente forma.

image

Y verificamos que la información viene desde nuestros servicios del backend.

image

Yey!! hemos conectado la aplicación cliente de angular con nuestro servidor backend de net core. Pero aun tenemos una situación. NO tenemos aun un registro de usuarios, a demás podemos acceder a todos los servicios sin estar logueados.

¡SI! PROBLEMAS DE SEGURIDAD. 😱

Por el momento y para no hacer mas largo el post lo dejaremos aquí. En la siguiente entrada resolveremos el problema que se nos presenta con la ayuda de Auth0.

Hasta la próxima.

Ya esta lista la segunda parte, puedes ir haciendo click aquí

Discussion (0)

Forem Open with the Forem app