Implementación de Estrategias de Autorización Personalizadas y Esquemas Múltiples en ASP.NET Core

Configuración de Políticas de Autorizaicón Básicas

En ASP.NET Core, el sistema de autorización permite definir reglas detalladas para controlar el acceso a los recursos. A continuación, se muestra cómo registrar una política basada en notificaciones (claims) en el contenedor de dependencias.

// Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("RequireITDepartment", policy =>
        policy.RequireClaim("Department", "IT"));
});

Aplicación de Políticas en Controladores

Una vez definida la política, se puede aplicar a controladores o acciones específicas utilizando el atributo [Authorize].

[ApiController]
[Route("api/[controller]")]
public class SecureDataController : ControllerBase
{
    [Authorize(Policy = "RequireITDepartment")]
    [HttpGet("confidential")]
    public IActionResult GetConfidentialData()
    {
        return Ok(new { Message = "Datos sensibles obtenidos con éxito.", Timestamp = DateTime.UtcNow });
    }
}

Manejador de Autenticación Personalizado

Para validar credenciales no estándar, como una clave de API en los encabezados, podemos implementar IAuthenticationHandler.

public class ApiKeyAuthenticationHandler : IAuthenticationHandler
{
    private AuthenticationScheme _scheme;
    private HttpContext _httpContext;

    public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
    {
        _scheme = scheme;
        _httpContext = context;
        return Task.CompletedTask;
    }

    public Task<AuthenticateResult> AuthenticateAsync()
    {
        var apiKey = _httpContext.Request.Headers["X-Api-Key"].FirstOrDefault();
        
        if (string.IsNullOrEmpty(apiKey) || apiKey != "super-secret-key-123")
        {
            return Task.FromResult(AuthenticateResult.Fail("Clave de API inválida o ausente."));
        }

        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, "system_admin"),
            new Claim("Department", "IT"),
            new Claim("ClearanceLevel", "High")
        };

        var identity = new ClaimsIdentity(claims, _scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, _scheme.Name);

        return Task.FromResult(AuthenticateResult.Success(ticket));
    }

    public Task ChallengeAsync(AuthenticationProperties properties)
    {
        _httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
        return Task.CompletedTask;
    }

    public Task ForbidAsync(AuthenticationProperties properties)
    {
        _httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
        return Task.CompletedTask;
    }
}

Si el manejador genera un valor de Department diferente al exigido por la política (por ejemplo, "HR" en lugar de "IT"), el acceso será denegado con un código 403.

Conceptos Clave: Requisitos de Autorización (Requirements)

El núcleo de la autorizaicón basada en políticas son los IAuthorizationRequirement. Cada requisito representa una condición que debe cumplirse. El framework incluye varias implementaciones predeterminadas:

  • AssertionRequirement: Evalúa una condición mediante un delegado o expresión booleana.
  • DenyAnonymousAuthorizationRequirement: Bloquea el acceso a usuarios no autenticados (configurado por defecto).
  • ClaimsAuthorizationRequirement: Verifica la presencia y el valor de un claim específico.
  • RolesAuthorizationRequirement: Comprueba si el usuario pertenece a uno o varios roles mediante IsInRole.
  • NameAuthorizationRequirement: Valida que el nombre del usuario coincida con el esperado.
  • OperationAuthorizationRequirement: Diseñado para autorizar operaciones específicas sobre recursos.

Cuando estas implementaciones no son suficientes, es posible crear requisitos y manejadores personalizados.

Creación de un Requirement y Handler Personalizado

Podemos definir un reqiusito que implemente tanto la interfaz de marcador como el manejador de lógica.

// Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("HighClearancePolicy", policy =>
        policy.Requirements.Add(new ClearanceLevelRequirement("High")));
});

public class ClearanceLevelRequirement : IAuthorizationRequirement
{
    public string RequiredLevel { get; }
    public ClearanceLevelRequirement(string level) => RequiredLevel = level;
}

public class ClearanceLevelHandler : AuthorizationHandler<ClearanceLevelRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClearanceLevelRequirement requirement)
    {
        var userClearance = context.User.FindFirst("ClearanceLevel")?.Value;

        if (userClearance != null && userClearance.Equals(requirement.RequiredLevel, StringComparison.OrdinalIgnoreCase))
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }

        return Task.CompletedTask;
    }
}

Nota: Para que el framework resuelva el manejador, debe registrarse en el contenedor de servicios: builder.Services.AddSingleton<IAuthorizationHandler, ClearanceLevelHandler>();

Aplicación de Múltiples Políticas

Es posible exigir que un endpoint cumpla con varias políticas simultáneamente. En este caso, todas las políticas deben evaluarse como exitosas.

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("RequireITDepartment", policy =>
        policy.RequireClaim("Department", "IT"));
        
    options.AddPolicy("HighClearancePolicy", policy =>
        policy.Requirements.Add(new ClearanceLevelRequirement("High")));
});

[ApiController]
[Route("api/[controller]")]
public class SecureDataController : ControllerBase
{
    [Authorize(Policy = "RequireITDepartment")]
    [Authorize(Policy = "HighClearancePolicy")]
    [HttpGet("top-secret")]
    public IActionResult GetTopSecretData()
    {
        return Ok(new { Data = "Información clasificada de nivel alto." });
    }
}

Integración de Múltiples Esquemas de Autenticación

En aplicaciones híbridas, es común soportar múltiples esquemas, como cookies para navegadores web y tokens/claves para clientes API.

// Program.cs
builder.Services.AddAuthentication("Cookies")
    .AddCookie(options =>
    {
        options.LoginPath = "/auth/login";
        options.AccessDeniedPath = "/auth/access-denied";
    });

builder.Services.AddAuthentication()
    .AddScheme<AuthenticationSchemeOptions, ApiKeyAuthenticationHandler>("ApiKeyScheme", options => { });

[ApiController]
[Route("api/[controller]")]
public class SecureDataController : ControllerBase
{
    [Authorize(Policy = "RequireITDepartment", AuthenticationSchemes = "ApiKeyScheme,Cookies")]
    [HttpGet("hybrid-endpoint")]
    public IActionResult GetHybridData()
    {
        return Ok("Acceso concedido mediante Cookie o API Key.");
    }
}

Vinculación Directa de Políticas a Esquemas Específicos

Para un control más granular, una política puede restringirse para que solo se evalúe si la autenticación proviene de un esquema particular.

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ApiOnlyPolicy", policy =>
    {
        policy.AddAuthenticationSchemes("ApiKeyScheme");
        policy.Requirements.Add(new ClearanceLevelRequirement("High"));
    });

    options.AddPolicy("WebOnlyPolicy", policy =>
    {
        policy.AddAuthenticationSchemes("Cookies");
        policy.RequireClaim("Department", "IT");
    });
});

Etiquetas: aspnetcore CSharp authorization Authentication claims

Publicado el 6-23 23:47