DEV Community

Cover image for .Net Core Autorización basada en políticas con Auth0
Dan Espinoza
Dan Espinoza

Posted on

.Net Core Autorización basada en políticas con Auth0

Llegamos a la ultima entrada de este tutorial, lo prometo.

Puedes ver las entradas anteriores desde aquí.

Primera Parte
Segunda Parte

En la entrada anterior, logramos configurar Auth0 en nuestras aplicaciones para que pudieran autenticar un usuario y realizar llamadas a las apis solo cuando el usuario haya iniciado sesión, lo que ahora haremos es hacer que una api sea accesible solo cuando el usuario tenga ciertos privilegios otorgados por un rol.

Manos a la obra!.

Validando scopes

Tenemos ya casi todo terminado, pero tenemos un gran problema de seguridad, si bien nuestros servicios están protegidos para que se puedan acceder únicamente cuando un usuario tenga una sesión activa, no queremos permitir que cualquier usuario acceda al tercer servicio, por lo que tenemos que implementar un paso más en nuestra capa de seguridad para proteger este endpoint.

Antes de tirar cualquier clase de código vayamos a nuestro dashboard de Auth0 y hagamos unos ajustes.

¡Manos a la obra!.

Vayamos al dashboard de Auth0 e ingresemos a la configuración de nuestra api.

Applications ⇒ API´s ⇒

Una vez ahí vayamos al tab de Permissions y agreguemos un permiso para nuestra API

image

Acabamos de agregar un permiso con un scope llamado read:permissions y añadimos la descripción de lo que hace.

La elección del nombre de nuestro scope personalizado puede ser cualquiera, pero un standard es nombrarlos de esta manera ya que explícitamente indica su función. nosotros usamos read⇒ para definir la lectura y permissions ⇒ para definir el nombre del endpoint. Tambien podríamos utilizar nombres como por ejemplo: view:events , register:events upload:image, transfer:founds ó quedarnos únicamente con los nombres read: update: write: y delete: Es tu decision pero trata de seguir ese standard.

Si quieres saber más acerca de los api-scopes puedes revisar la documentación oficial de Auth0 dando click aquí

Antes de salir de la configuración de la API habilitemos la configuración RBAC ( Rol based access control ó control de acceso basado en roles). Para eso vayamos a el tab de settings en el apartado de RBAC.

image

image

Al habilitar RBCA, las políticas de autorización serán forzadas para esta API, eso quiere decir que los roles y permisos asignados a un usuario serán evaluados cuando el usuario intente acceder al recurso.

También habilitamos la segunda opción para que los permisos del usuario se incluyan en el token de acceso.

Teniendo configurada nuestra API toca que configuremos un rol de usuario que incluya el permiso que acabamos de agregar.

Vayamos a la siguiente ruta en el dashboard.

User Management ⇒ Roles

Una vez ahí click en + CREATE ROLE, nos pedirá que asignemos un nombre y una descripción. Los agregamos y damos click en CREATE.

image

Ahora vamos a la pestaña Permissions y agregamos el permiso que queremos que tenga ese Rol.

image

nos pedirá que seleccionemos la API a la cual tendrá permiso y seleccionamos el permiso que creamos anteriormente. Finalmente damos click en ADD PERMISSIONS

image

image

Ahora toca asignarle el rol a un usuario. Para ello vayamos a

User Management ⇒ Users

En este punto del tutorial deberíamos de tener un usuario el cual creamos al realizar las primeras pruebas. Si estuviste jugando en este apartado y no tienes ningún usuario es momento de crearlo.

Haz click en +CREATE USER y llena los datos, posteriormente crea un segundo usuario.

En mi caso ya tengo tengo dos usuarios creados.

image

Considerando esto, le asignaré el Rol que acabamos de crear solo a uno de estos usuarios, el segundo usuario NO TENDRÁ NINGUN ROL. Recuerda, esto es importante.

Para otorgarle un Rol al usuario basta con hacer click en el botón con los tres puntos y dar click en Assign Roles.

image

Seleccionamos el rol que le queremos asignar y finalizamos dando click en el boton ASSIGN

image

Genial!! hemos terminado las configuraciones, ahora hagamos los últimos cambios en nuestro código del backend.

Policy Based Authorization .Net Core

Una política de autorización consiste en que uno o mas requerimientos sean registrados como parte de la configuración del authorization service. Para poder validar que nuestro usuario cumpla con las reglas que necesitamos, crearemos dos clases con las cuales nos vamos a ayudar.

Creemos un requerimiento de autorización y llamémoslo HasScopeRequirement, este requerimiento se encargará de revisar si el scope claim issued emitido por nuestro tenant de Auth0 está presente en el request. De ser así, si el scope claim existe, entonces el requirement revisará que el scope claim contenga el request scope. No te asustes, suena más complejo de lo que en verdad es.

Si quieres saber más de la autorización basada en políticas en Asp .NetCore da click aquí.

Veámoslo en código.

En la raíz de nuestro proyecto de netcore api-resource-server creemos una nueva carpeta y nombrémosla Utils, dentro de utils creemos otra de nombre Auth. Dentro de auth crearemos dos clases y las nombraremos HasScopeRequirement y HasScopeHandler . la estructura quedará de esta forma

image

Vayamos a la clase HasScopeRequirement.cs y escribamos el siguiente código.

// ### HasScopeRequirement.cs ###

using Microsoft.AspNetCore.Authorization;
using System;

namespace api_resource_server.Utils.Auth
{
    public class HasScopeRequirement: IAuthorizationRequirement
    {
        public string Issuer { get; }
        public string Scope { get; }

        public HasScopeRequirement(string scope, string issuer)
        {
            Scope = scope ?? throw new ArgumentNullException(nameof(scope));
            Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Si observamos la clase está implementando de la interfaz IAuthorizationRequirement, esta interfaz es un servicio de marcador la cual no contiene ningún método, pero su función es rastrear si la autorización es exitosa.

Su constructor simplemente se encargará de validar que tenga un scope y un Issuer, de lo contrario fallará.

Ahora es turno de la clase HasScopeHandler.cs

// ### HasScopeHandler.cs ###

using Microsoft.AspNetCore.Authorization;
using System.Linq;
using System.Threading.Tasks;

namespace api_resource_server.Utils.Auth
{
    public class HasScopeHandler : AuthorizationHandler<HasScopeRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement)
        {
            if (!context.User.HasClaim(c => c.Type == "permissions" && c.Issuer == requirement.Issuer))
            {
                return Task.CompletedTask;
            }

            var scopes = context.User.FindFirst(c => c.Type == "permissions" && c.Issuer == requirement.Issuer).Value.Split(' ');

            if (scopes.Any(s=> s== requirement.Scope))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Acabamos de crear un manejador de scopes, esta clase extiende de AuthorizationHandler a la cual le pasamos como tipo la clase HasScopeRequirement que creamos anteriormente. Posteriormente comprueba que nuestro contexto tenga un claim de tipo scope y que su issuer sea igual al que protege el servicio, de no ser así no lo deja pasar el request. Si cumple con lo solicitado el contexto acepta el requirement y permite el paso del request.

Ahora toca añadir una nueva política a nuestro middleware y configurar nuestro HasScopeHandler como singleton. En el Startup.cs añadimos el siguiente Código

// ### Startup.cs ###

public class Startup
{
     //Código existenete...
    const string ReadPermissions = "read:permissions";

    public void ConfigureServices(IServiceCollection services)
    {
          // Código existente...
          services.AddAuthorization(options =>
          {
               options.AddPolicy(
                name: ReadPermissions,
                policy => policy.Requirements.Add(new HasScopeRequirement(
                  ReadPermissions, "https://ng-dotnet-auth.us.auth0.com/"))
                );
           });

           services.AddSingleton<IAuthorizationHandler, HasScopeHandler>();
           // Código existente...
        }
        // Código existente...
}
Enter fullscreen mode Exit fullscreen mode

Finalmente protegeremos nuestro endpoint con la política que acabamos de crear. Quedando de la siguiente manera

// ### TestController.cs ###

// Código existente...
[Authorize("read:permissions")]
[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

Ahora si lo que tanto esperábamos...

Pruebas!!!! 🤓🤓🤓

Primero realicemos una prueba sin sesión de usuario.

image

Observamos como sin sesión solo podemos acceder al servicio publico, los otros dos servicios nos piden iniciar sesión.

Ahora hagamos otra prueba. Iniciemos sesión con la cuenta que NO tiene asignado un rol.

image

Con el usuario sin Rol asignado vemos como podemos acceder tanto al servicio publico como al privado, pero en el caso del privado + permisos, nos indica que no tenemos los permisos necesarios.

Si miramos el apartado de network, en las developer tools de chrome veremos lo siguiente:

image

Los dos primeros retornan un estatus 200 que la petición fue satisfactoria y la tercera retorna un estatus 403 que significa forbidden en español prohibido y, en palabras mortales, que no tenemos permisos para ver ese contenido.

Todo esto significa que logramos nuestro objetivo, proteger la API incluso cuando el usuario haya iniciado sesión.

Pero, ¿que pasa con un usuario que si tiene el rol necesario?

Iniciemos sesión y veamos.

image

¡¡Genial!! Nuestro usuario con rol asignado tiene los permisos para realizar peticiones a los tres servicios.

Cumplimos nuestra misión. ¡¡Hurra!! 🎉🥳🎈

Reacapitulando

Después de un buen rato, configurando una integración de tres ecosistemas distintos recapitulemos lo que hemos conseguido.

  • Conocimos Auth0 y la diferencia entre una APP y una API.
  • Configuramos Auth0 para proteger nuestra primer aplicación SPA y Nuestra API.
  • Creamos una aplicación en angular, con la cual puedes realizar el registro e inicio de sesión de un usuario mediante el sdk de auth0.
  • Vimos una ligera introducción de lo que es JSON Web Token y en que consiste.
  • Configuramos en angular, un interceptor para incluir un JSON Web Token en los request coincidentes con nuestros endpoints, gracias al sdk que provee auth0.
  • Consumimos tres servicios diferentes para conectar nuestro cliente con el backend mediante APIRest con HTTPModule de Angular.
  • Creamos un proyecto backend con Net Core Web API.
  • Configuramos el middleware del backend para permitir la autenticación de usuarios mediante JWT y añadimos una política para permitir los orígenes cruzados.
  • Configuramos el middleware para añadir una política basada en Autorización.
  • Creamos un manejador que permitiera validar si en el JWT existe uno o mas scopes y validar los requirements gracias a la interfaz IAuthorizationRequirement y a la clase AuthorizationHandler.
  • Conocimos algunas de las principales respuestas del protocolo HTTP.

WOW! Fueron bastantes temas aprendidos o repasados.

¡¡Felicidades!!.

Este fue mi primer articulo y mi primer tutorial paso a paso, de lo que espero seguir sacando muchos mas.

Espero que haya sido de tu agrado, pero en especial, que te sea de mucha utilidad. Al final de cuentas esa es la principal razón de haberlo realizado.

Te dejo la url del repositorio por si te perdiste de algún paso, puedas comparar el código.

Repositorio en Github

Sígueme en twitter @yosisoydanny o en Linkedin @odprz

Hasta la otra!

Top comments (0)