Adam Stapleton
Adam Stapleton

Reputation: 635

How can I see which AuthorizeAttribute Failed ASP.NET Core

I am trying to work out if there is an easy way to get ASP.NET Core to log which [Authorize] attribute is failing. I have a mixture of "Role" and "Policy" authorize attributes but whenever a single one fails the logs just show:

enter image description here

Obviously this is the correct behaviour and it doesn't let someone in with incorrect permissions however if you have multiple attributes it's a bit of a pain to have to go and work out which one failed. If the log simply showed Authorization failed for Policy X then that would be really easy to then find what's failing.

Does anyone know if it's currently possible to make this happen through some option I'm unaware of?

EDIT: For example: If I had [Authorize(Policy = "Policy 1")] and [Authorize(Policy = "Policy 2")] and only "Policy 2" failed. I would like to see something that tells me that it was "Policy 2" that failed.

EDIT: For anyone still coming across this question this has now been implemented by Microsoft and is part of .NET 5.0, see issue https://github.com/aspnet/AspNetCore/issues/7789

Upvotes: 2

Views: 3835

Answers (3)

Edward
Edward

Reputation: 29976

For Roles and Policy, they are translated to requirements like RolesAuthorizationRequirement or your custom requirement like MinimumAgeRequirement.

For Authorization failed., this is logged by DefaultAuthorizationService in AuthorizeAsync, you may not able to get the exact name like Policy 1 and Policy 2. You could get the requirements for Policy.

Try to check whether workaround below meets your requirement.

  1. Implement custom DefaultAuthorizationService

    public class CustomAuthorizationService : DefaultAuthorizationService, IAuthorizationService
    {
        private readonly AuthorizationOptions _options;
        private readonly IAuthorizationHandlerContextFactory _contextFactory;
        private readonly IAuthorizationHandlerProvider _handlers;
        private readonly IAuthorizationEvaluator _evaluator;
        private readonly IAuthorizationPolicyProvider _policyProvider;
        private readonly ILogger _logger;
    
        public CustomAuthorizationService(IAuthorizationPolicyProvider policyProvider
            , IAuthorizationHandlerProvider handlers
            , ILogger<DefaultAuthorizationService> logger
            , IAuthorizationHandlerContextFactory contextFactory
            , IAuthorizationEvaluator evaluator
            , IOptions<AuthorizationOptions> options) 
            : base(policyProvider, handlers, logger, contextFactory, evaluator, options)
        {
            _options = options.Value;
            _handlers = handlers;
            _policyProvider = policyProvider;
            _logger = logger;
            _evaluator = evaluator;
            _contextFactory = contextFactory;
        }
    
        public new async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements)
        {
            if (requirements == null)
            {
                throw new ArgumentNullException(nameof(requirements));
            }
    
            var authContext = _contextFactory.CreateContext(requirements, user, resource);
            var handlers = await _handlers.GetHandlersAsync(authContext);
            foreach (var handler in handlers)
            {
                await handler.HandleAsync(authContext);
                if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
                {
                    break;
                }
            }
    
            var result = _evaluator.Evaluate(authContext);
            if (result.Succeeded)
            {
                _logger.LogInformation($"Authorization is succeeded for { JsonConvert.SerializeObject(requirements) }" );
                //_logger.UserAuthorizationSucceeded();
            }
            else
            {
                //var r = result.Failure.FailedRequirements.Select(requirement => new { Requirement = requirement.GetType() });
                var json = JsonConvert.SerializeObject(result.Failure.FailedRequirements);
                _logger.LogInformation($"Authorization is failed for { json }");
                //_logger.UserAuthorizationFailed();
            }
            return result;
        }
    
    }
    
  2. Replace built-in DefaultAuthorizationService

    services.AddAuthorization(config =>
    {
        config.AddPolicy("T1", policy => policy.AddRequirements(new MinimumAgeRequirement(21)));
    });
    
    services.Replace(ServiceDescriptor.Transient<IAuthorizationService, CustomAuthorizationService>());
    

Upvotes: 6

Adam Stapleton
Adam Stapleton

Reputation: 635

Microsoft are implementing this by default in .NET 5.0, see the related GitHub issue for details and links to PR.

https://github.com/aspnet/AspNetCore/issues/7789

Upvotes: 2

Derviş Kayımbaşıoğlu
Derviş Kayımbaşıoğlu

Reputation: 30545

you can handle and log this inside Middlewares

public class AuthHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ErrorHandlingMiddleware> _logger;

    public AuthHandlerMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context, IHostingEnvironment env /* other scoped dependencies */)
    {  

        await _next(context);

        if (context.Response.StatusCode == 401)
           _logger.LogInformation($"'{context.User.Identity.Name}' is unauthorized");
    }
}

In your starup config,

public void Configure(IApplicationBuilder app, ... )
{
      ....
      app.UseMiddleware<AuthHandlerMiddleware>();
}

Upvotes: 0

Related Questions