I can't get AuthorizationFailureReason to output the reason in the response

Viewed 438

I have a custom authorization policy where I'd like to output the reason to the output to help the consumer understand why he or she gets a 403 forbidden result. I have the following code:

internal class MyRequirementHandler : AuthorizationHandler<MyRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
    {
        if (myCondition)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail(new AuthorizationFailureReason(this, "Reason why failing"));
        }

        return Task.CompletedTask;
    }
}

I would expect this to output "Reason why failing" to the body of the response, but I don't. I would get it to work by doing this instead:

if (context.Resource is DefaultHttpContext mvcContext)
{
    mvcContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
    await mvcContext.HttpContext.Response.WriteAsync("Reason why failing");
}

But that doesn't feel like the correct way.

Grateful for any help and or input!

1 Answers

You can use IAuthorizationMiddlewareResultHandler to get AuthorizationFailureReason information. see Customize the behavior of AuthorizationMiddleware.

In my case, I use global exception middleware to handle exception, so I need to rethrow exception. You can see below example.

// Implement IAuthorizationMiddlewareResultHandler
public class ForbiddenAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
    private readonly AuthorizationMiddlewareResultHandler defaultHandler = new();

    public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
    {
        if (authorizeResult.Forbidden)
        {
            var failureReasons = authorizeResult.AuthorizationFailure?.FailureReasons.FirstOrDefault();
            throw new UnauthorizedAccessException(failureReasons?.Message ?? "You do not have permission to access.");
        }

        await defaultHandler.HandleAsync(next, context, policy, authorizeResult);
    }
}

// Handle Global Exception
public class ExceptionHandleMiddleware
{
    private readonly ILogger<ExceptionHandleMiddleware> _logger;

    private readonly RequestDelegate _next;
     
    public ExceptionHandleMiddleware(ILogger<ExceptionHandleMiddleware> logger, RequestDelegate next)
    {
        _logger = logger;
        _next = next;        
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (System.Exception e)
        {
            _logger.LogError(e, "Catch some exception.");
            // Handle UnauthorizedAccessException and get your "Reason why failing" message         
        }
    }
}
 
// Don't forget Register DI
services.AddSingleton<IAuthorizationMiddlewareResultHandler, ForbiddenAuthorizationMiddlewareResultHandler>();

Related