using System.Text.Json; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; namespace SIGCM2.Api.Authorization; /// /// Custom IAuthorizationMiddlewareResultHandler that emits a structured ProblemDetails /// response for 403 Forbidden outcomes (authenticated user, missing permission). /// /// For 401 Unauthorized and successful outcomes, delegates to the default handler /// so the existing JWT Bearer challenge flow is unaffected (REQ-B-07). /// /// Registered as singleton in Program.cs — depends only on framework services. /// public sealed class ForbiddenProblemDetailsHandler : IAuthorizationMiddlewareResultHandler { private static readonly AuthorizationMiddlewareResultHandler DefaultHandler = new(); private static readonly JsonSerializerOptions SerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; public async Task HandleAsync( RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) { // Only intercept 403s for authenticated users. // If the user is not authenticated, the 401 challenge is handled by JwtBearer (REQ-B-07). if (authorizeResult.Forbidden && context.User.Identity?.IsAuthenticated == true) { var requiredPermission = context.Items["RequiredPermission"] as string; context.Response.StatusCode = StatusCodes.Status403Forbidden; context.Response.ContentType = "application/problem+json; charset=utf-8"; var problem = new { type = "https://sigcm2.local/errors/forbidden", title = "Acceso denegado", status = 403, detail = "No tenés el permiso requerido para ejecutar esta acción.", permisoRequerido = requiredPermission, }; await context.Response.WriteAsync( JsonSerializer.Serialize(problem, SerializerOptions)); return; } // Delegate 401 challenges and successful outcomes to the default handler await DefaultHandler.HandleAsync(next, context, policy, authorizeResult); } }