Saltar a contenido

Authorization

Author: Olivera Lucas

Se incorporo lógica para que los endpoints de nuestras aplicaciones puedas autorizar. Esto nos permite bloquear el acceso a los endpoint a usuarios que no tienen el nivel de privilegios adecuado.

Prerequsistos

  1. .NET 6
  2. Andreani.ARQ.WebHost >= 6.3.0
  3. ver: CustomPolicy
  4. ver: Administración de usuarios

Configuración

Para configurar nuestras políticas de autorización debemos:

  1. Definir las políticas.
  2. Registrar las políticas al flujo de autorización.
  3. Identificar los endpoints que requieren autorización.

Definición de politicas de autorización.

Las políticas serán clases que implementen la interface IAzureAdB2CPolicy

public interface IAzureAdB2CPolicy<T>
{
    public Task<AuthorizeResponse> IsAuthorized(UserAzureAdB2C<T> user);
}

NOTA: El generic T corresponde a la estructura que almacenamos en Azure AD B2C.

Ejemplo 1:

public class Policy : IAzureAdB2CPolicy<UserData>
{
    public Task<AuthorizeResponse> IsAuthorized(UserAzureAdB2C<UserData> user)
    {
        if (user.Raw.Role != null)
            return Task.FromResult(new AuthorizeResponse() 
            { 
                IsAuthorized = true 
            });

        return Task.FromResult(new AuthorizeResponse() 
        { 
            IsAuthorized = false, // no tiene permiso
            StatusCode = System.Net.HttpStatusCode.Forbidden, 
            Body = new AuthorizeBodyResponse
            {
                Message = "El usuario no tiene las claims de raw"
            }
        });

    }
}

En este ejemplo UserData corresponde a la estructura que se almacena en Azure AD B2C, esto permite la deserialización del token y la asignación del campo RAW.

public record struct UserData
{
    public string Role { get; set; }
    public List<string> Apps { get; set; }
}

Como podemos ver, la clase Policy implementa IsAuthorized que es el método que se ejecutará para detectar si se autoriza o no al usuario autenticado. Este método devuelve una clase AuthorizeResponse la cual permite manipular la información que se mostrará en el response de la petición

Definición: AuthorizeResponse

public class AuthorizeResponse
{
    public bool IsAuthorized { get; set; }
    public HttpStatusCode StatusCode { get; set; }
    public Dictionary<string, string> Headers { get; set; }
    public AuthorizeBodyResponse Body { get; set; }

}
public class AuthorizeBodyResponse
{
    public string Message { get; set; }
}
Campo Descripción
IsAuthorized boleano que identifica si el usuario tiene permiso o no. true tiene permisos, false no tiene permiso
StatusCode Status code que se devolverá en la petición http
Headers (opcional) Headers que se incorporarán en la respuesta
Body (opcional) Mensaje que se devolverá en el body de la respuesta

Ejemplo 2:

Creamos una política que valide si el role es admin

public class PolicyIsAdmin : IAzureAdB2CPolicy<UserData>
{
    public Task<AuthorizeResponse> IsAuthorized(UserAzureAdB2C<UserData> user)
    {
        if (user.Raw.Role == "Admin")
            return Task.FromResult(new AuthorizeResponse() 
            { 
                IsAuthorized = true 
            });

        return Task.FromResult(new AuthorizeResponse()
        {
            IsAuthorized = false,
            StatusCode = System.Net.HttpStatusCode.Forbidden,
            Body = new AuthorizeBodyResponse 
            { 
                Message = "El usuario no es administrador" 
            },
            Headers = new Dictionary<string, string>
            {
                { "Invalid", "Is not Admin" }
            }
        });
    }
}
Una vez que definamos nuestras policías, debemos configurarlas

Registrar las politicas al flujo de autorización.

Debemos ir a nuestro archivo Program.cs o Startup.cs (en la vieja configuración) y configurar el middleware.

// Ejemplo con nueva defición de Program

// Configuramos el Services para las DI
builder.Services.AddScoped<Policy>();
builder.Services.AddScoped<PolicyIsAdmin>();
builder.Services.AddAuthorizationCustomPolicy<UserData>();

var app = builder.Build();

// Configuramos el middleware
app.ConfigureAndreani(
    middleware: (opt) => opt.UseCustomPolicy<UserData>()
    .AddDefaultCustomPolicy<Policy>()
    .AddCustomPolicy<PolicyIsAdmin>("isAdmin")
    .Build()
    );

NOTA: ver Configuración Middleware WebHost

Podemos ver que en la configuración del middleware se están configurando dos políticas.

  1. Default Policy: Se ejecuta cuando un endpoint o controller tiene el atributo por defecto o el atributo con el valor DefaultPolicy
  2. Custom Policy: Se ejecuta cuando un endpoint o controller tiene el atributo configurado con el valor que se le asigna en el middleware, en este caso isAdmin.

Configurar los endpoint.

Debemos configurar los endpoints para que se ejecute el análisis de las políticas cuando estos son invocados, para eso utilizaremos el atributo AuthorizeCustomPolicy, El atributo puede autorizar a un endpoint especifico o a todo un controller con la misma autorización

Ejemplo 1: Controller

[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[AuthorizeCustomPolicy()]
public class PersonController : ApiControllerBase
{

}

Esto nos asegura que todos los endpoints dentro del controller ejecutaran la política por defecto. En el Ejemplo 1: tenemos la política que se configuro por defecto. Por lo que todos los endpoints del controller tendrán esta validación.

Warning

Si configuramos el atributo de manera global en el controller todos los endpoint heredaran esta propiedad, por lo que si en algunos endpoints tenemos diferentes políticas se sumaran a la que configuramos globalmente.

Ejemplo 2: Endpoint

Configuración default policy

[HttpGet()]
[ProducesResponseType(typeof(List<PersonDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(List<Notify>), StatusCodes.Status400BadRequest)]
[Authorize(AuthenticationSchemes = WebHostConstants.AzureAdB2cAuthenticationScheme)] // config de autenticación con azure Ad b2c
[AuthorizeCustomPolicy()] // config autorización
public async Task<IActionResult> Get()
{
    return Ok();
}

Configuración custom policy

[HttpGet()]
[ProducesResponseType(typeof(List<PersonDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(List<Notify>), StatusCodes.Status400BadRequest)]
[Authorize(AuthenticationSchemes = WebHostConstants.AzureAdB2cAuthenticationScheme)] // config de autenticación con azure Ad b2c
[AuthorizeCustomPolicy("isAdmin")] // config autorización
public async Task<IActionResult> GetAdmin()
{
    return Ok();
}

Configuration multi custom policy

[HttpGet()]
[ProducesResponseType(typeof(List<PersonDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(List<Notify>), StatusCodes.Status400BadRequest)]
[Authorize(AuthenticationSchemes = WebHostConstants.AzureAdB2cAuthenticationScheme)] // config de autenticación con azure Ad b2c
[AuthorizeCustomPolicy("isAdmin", "isUser", "isValidSucursal")] // config autorización
public async Task<IActionResult> Get()
{
    return Ok();
}

Warning

Las policy isUser y isValidSucursal son políticas que no hemos configurados en este ejemplo y su documentación es meramente ejemplar.

Importante

El atributo AuthorizeCustomPolicy puede funcionar con o sin la configuración de autenticación. En caso de no implementar Authorize debemos asegurarnos de que llegue un token valido en el campo Authorization del header del request.

Obtener el usuario

Para obtener el usuario autorizado, utilizaremos el método GetUserAzureAdB2c que poseen los controllers. Este método nos devolverá un objeto hidratado con la información del token y la deserialización del campo raw

Ejemplo de Obtener usuario:

[HttpGet()]
[ProducesResponseType(typeof(List<PersonDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(List<Notify>), StatusCodes.Status400BadRequest)]
[Authorize(AuthenticationSchemes = WebHostConstants.AzureAdB2cAuthenticationScheme)] // config de autenticación con azure Ad b2c
[AuthorizeCustomPolicy()] // config autorización
public async Task<IActionResult> Get()
{
    var user = GetUserAzureAdB2c<UserData>();
    return Ok();
}

user tendrá la información del token.

public class UserAzureAdB2C<T>
{
    public string Email { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
    public string Tfp { get; set; }
    public T Raw { get; set; }
}
user

Evidencias de uso:

forbidden